Merge "renamed audio policy output flag."
diff --git a/api/current.txt b/api/current.txt
index 5babf23..cce28a4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11815,6 +11815,7 @@
     enum_constant public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR;
     enum_constant public static final android.net.NetworkInfo.DetailedState SCANNING;
     enum_constant public static final android.net.NetworkInfo.DetailedState SUSPENDED;
+    enum_constant public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK;
   }
 
   public static final class NetworkInfo.State extends java.lang.Enum {
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index b5cef81..1900301 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -208,6 +208,11 @@
                 mConfiguration.label,
                 SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);
 
+        setSyncMode();
+        setPageSize();
+        setAutoCheckpointInterval();
+        setJournalSizeLimit();
+        setJournalModeFromConfiguration();
         setLocaleFromConfiguration();
     }
 
@@ -231,6 +236,44 @@
         }
     }
 
+    private void setSyncMode() {
+        if (!mConfiguration.isInMemoryDb()) {
+            execute("PRAGMA synchronous=" + SQLiteGlobal.getSyncMode(), null, null);
+        }
+    }
+
+    private void setPageSize() {
+        if (!mConfiguration.isInMemoryDb()) {
+            execute("PRAGMA page_size=" + SQLiteGlobal.getDefaultPageSize(), null, null);
+        }
+    }
+
+    private void setAutoCheckpointInterval() {
+        if (!mConfiguration.isInMemoryDb()) {
+            executeForLong("PRAGMA wal_autocheckpoint=" + SQLiteGlobal.getWALAutoCheckpoint(),
+                    null, null);
+        }
+    }
+
+    private void setJournalSizeLimit() {
+        if (!mConfiguration.isInMemoryDb()) {
+            executeForLong("PRAGMA journal_size_limit=" + SQLiteGlobal.getJournalSizeLimit(),
+                    null, null);
+        }
+    }
+
+    private void setJournalModeFromConfiguration() {
+        if (!mConfiguration.isInMemoryDb()) {
+            String result = executeForString("PRAGMA journal_mode=" + mConfiguration.journalMode,
+                    null, null);
+            if (!result.equalsIgnoreCase(mConfiguration.journalMode)) {
+                Log.e(TAG, "setting journal_mode to " + mConfiguration.journalMode
+                        + " failed for db: " + mConfiguration.label
+                        + " (on pragma set journal_mode, sqlite returned:" + result);
+            }
+        }
+    }
+
     private void setLocaleFromConfiguration() {
         nativeSetLocale(mConnectionPtr, mConfiguration.locale.toString());
     }
@@ -246,7 +289,9 @@
             }
         }
 
-        // Remember whether locale has changed.
+        // Remember what changed.
+        boolean journalModeChanged = !configuration.journalMode.equalsIgnoreCase(
+                mConfiguration.journalMode);
         boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
 
         // Update configuration parameters.
@@ -255,6 +300,11 @@
         // Update prepared statement cache size.
         mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
 
+        // Update journal mode.
+        if (journalModeChanged) {
+            setJournalModeFromConfiguration();
+        }
+
         // Update locale.
         if (localeChanged) {
             setLocaleFromConfiguration();
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 236948e..3562e89 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -92,13 +92,25 @@
             new ArrayList<SQLiteConnection>();
     private SQLiteConnection mAvailablePrimaryConnection;
 
+    // Describes what should happen to an acquired connection when it is returned to the pool.
+    enum AcquiredConnectionStatus {
+        // The connection should be returned to the pool as usual.
+        NORMAL,
+
+        // The connection must be reconfigured before being returned.
+        RECONFIGURE,
+
+        // The connection must be closed and discarded.
+        DISCARD,
+    }
+
     // Weak references to all acquired connections.  The associated value
-    // is a boolean that indicates whether the connection must be reconfigured
-    // before being returned to the available connection list.
+    // indicates whether the connection must be reconfigured before being
+    // returned to the available connection list or discarded.
     // For example, the prepared statement cache size may have changed and
-    // need to be updated.
-    private final WeakHashMap<SQLiteConnection, Boolean> mAcquiredConnections =
-            new WeakHashMap<SQLiteConnection, Boolean>();
+    // need to be updated in preparation for the next client.
+    private final WeakHashMap<SQLiteConnection, AcquiredConnectionStatus> mAcquiredConnections =
+            new WeakHashMap<SQLiteConnection, AcquiredConnectionStatus>();
 
     /**
      * Connection flag: Read-only.
@@ -168,7 +180,7 @@
     private void open() {
         // Open the primary connection.
         // This might throw if the database is corrupt.
-        mAvailablePrimaryConnection = openConnectionLocked(
+        mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
                 true /*primaryConnection*/); // might throw
 
         // Mark the pool as being open for business.
@@ -209,16 +221,7 @@
 
                 mIsOpen = false;
 
-                final int count = mAvailableNonPrimaryConnections.size();
-                for (int i = 0; i < count; i++) {
-                    closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
-                }
-                mAvailableNonPrimaryConnections.clear();
-
-                if (mAvailablePrimaryConnection != null) {
-                    closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
-                    mAvailablePrimaryConnection = null;
-                }
+                closeAvailableConnectionsAndLogExceptionsLocked();
 
                 final int pendingCount = mAcquiredConnections.size();
                 if (pendingCount != 0) {
@@ -254,21 +257,27 @@
         synchronized (mLock) {
             throwIfClosedLocked();
 
-            final boolean poolSizeChanged = mConfiguration.maxConnectionPoolSize
-                    != configuration.maxConnectionPoolSize;
-            mConfiguration.updateParametersFrom(configuration);
+            if (mConfiguration.openFlags != configuration.openFlags) {
+                // Try to reopen the primary connection using the new open flags then
+                // close and discard all existing connections.
+                // This might throw if the database is corrupt or cannot be opened in
+                // the new mode in which case existing connections will remain untouched.
+                SQLiteConnection newPrimaryConnection = openConnectionLocked(configuration,
+                        true /*primaryConnection*/); // might throw
 
-            if (poolSizeChanged) {
-                int availableCount = mAvailableNonPrimaryConnections.size();
-                while (availableCount-- > mConfiguration.maxConnectionPoolSize - 1) {
-                    SQLiteConnection connection =
-                            mAvailableNonPrimaryConnections.remove(availableCount);
-                    closeConnectionAndLogExceptionsLocked(connection);
-                }
+                closeAvailableConnectionsAndLogExceptionsLocked();
+                discardAcquiredConnectionsLocked();
+
+                mAvailablePrimaryConnection = newPrimaryConnection;
+                mConfiguration.updateParametersFrom(configuration);
+            } else {
+                // Reconfigure the database connections in place.
+                mConfiguration.updateParametersFrom(configuration);
+
+                closeExcessConnectionsAndLogExceptionsLocked();
+                reconfigureAllConnectionsLocked();
             }
 
-            reconfigureAllConnectionsLocked();
-
             wakeConnectionWaitersLocked();
         }
     }
@@ -310,8 +319,8 @@
      */
     public void releaseConnection(SQLiteConnection connection) {
         synchronized (mLock) {
-            Boolean mustReconfigure = mAcquiredConnections.remove(connection);
-            if (mustReconfigure == null) {
+            AcquiredConnectionStatus status = mAcquiredConnections.remove(connection);
+            if (status == null) {
                 throw new IllegalStateException("Cannot perform this operation "
                         + "because the specified connection was not acquired "
                         + "from this pool or has already been released.");
@@ -320,18 +329,8 @@
             if (!mIsOpen) {
                 closeConnectionAndLogExceptionsLocked(connection);
             } else if (connection.isPrimaryConnection()) {
-                assert mAvailablePrimaryConnection == null;
-                try {
-                    if (mustReconfigure == Boolean.TRUE) {
-                        connection.reconfigure(mConfiguration); // might throw
-                    }
-                } catch (RuntimeException ex) {
-                    Log.e(TAG, "Failed to reconfigure released primary connection, closing it: "
-                            + connection, ex);
-                    closeConnectionAndLogExceptionsLocked(connection);
-                    connection = null;
-                }
-                if (connection != null) {
+                if (recycleConnectionLocked(connection, status)) {
+                    assert mAvailablePrimaryConnection == null;
                     mAvailablePrimaryConnection = connection;
                 }
                 wakeConnectionWaitersLocked();
@@ -339,17 +338,7 @@
                     mConfiguration.maxConnectionPoolSize - 1) {
                 closeConnectionAndLogExceptionsLocked(connection);
             } else {
-                try {
-                    if (mustReconfigure == Boolean.TRUE) {
-                        connection.reconfigure(mConfiguration); // might throw
-                    }
-                } catch (RuntimeException ex) {
-                    Log.e(TAG, "Failed to reconfigure released non-primary connection, "
-                            + "closing it: " + connection, ex);
-                    closeConnectionAndLogExceptionsLocked(connection);
-                    connection = null;
-                }
-                if (connection != null) {
+                if (recycleConnectionLocked(connection, status)) {
                     mAvailableNonPrimaryConnections.add(connection);
                 }
                 wakeConnectionWaitersLocked();
@@ -357,6 +346,25 @@
         }
     }
 
+    // Can't throw.
+    private boolean recycleConnectionLocked(SQLiteConnection connection,
+            AcquiredConnectionStatus status) {
+        if (status == AcquiredConnectionStatus.RECONFIGURE) {
+            try {
+                connection.reconfigure(mConfiguration); // might throw
+            } catch (RuntimeException ex) {
+                Log.e(TAG, "Failed to reconfigure released connection, closing it: "
+                        + connection, ex);
+                status = AcquiredConnectionStatus.DISCARD;
+            }
+        }
+        if (status == AcquiredConnectionStatus.DISCARD) {
+            closeConnectionAndLogExceptionsLocked(connection);
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Returns true if the session should yield the connection due to
      * contention over available database connections.
@@ -407,9 +415,10 @@
     }
 
     // Might throw.
-    private SQLiteConnection openConnectionLocked(boolean primaryConnection) {
+    private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
+            boolean primaryConnection) {
         final int connectionId = mNextConnectionId++;
-        return SQLiteConnection.open(this, mConfiguration,
+        return SQLiteConnection.open(this, configuration,
                 connectionId, primaryConnection); // might throw
     }
 
@@ -443,6 +452,30 @@
     }
 
     // Can't throw.
+    private void closeAvailableConnectionsAndLogExceptionsLocked() {
+        final int count = mAvailableNonPrimaryConnections.size();
+        for (int i = 0; i < count; i++) {
+            closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
+        }
+        mAvailableNonPrimaryConnections.clear();
+
+        if (mAvailablePrimaryConnection != null) {
+            closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
+            mAvailablePrimaryConnection = null;
+        }
+    }
+
+    // Can't throw.
+    private void closeExcessConnectionsAndLogExceptionsLocked() {
+        int availableCount = mAvailableNonPrimaryConnections.size();
+        while (availableCount-- > mConfiguration.maxConnectionPoolSize - 1) {
+            SQLiteConnection connection =
+                    mAvailableNonPrimaryConnections.remove(availableCount);
+            closeConnectionAndLogExceptionsLocked(connection);
+        }
+    }
+
+    // Can't throw.
     private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
         try {
             connection.close(); // might throw
@@ -453,8 +486,12 @@
     }
 
     // Can't throw.
+    private void discardAcquiredConnectionsLocked() {
+        markAcquiredConnectionsLocked(AcquiredConnectionStatus.DISCARD);
+    }
+
+    // Can't throw.
     private void reconfigureAllConnectionsLocked() {
-        boolean wake = false;
         if (mAvailablePrimaryConnection != null) {
             try {
                 mAvailablePrimaryConnection.reconfigure(mConfiguration); // might throw
@@ -463,7 +500,6 @@
                         + mAvailablePrimaryConnection, ex);
                 closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
                 mAvailablePrimaryConnection = null;
-                wake = true;
             }
         }
 
@@ -478,27 +514,30 @@
                 closeConnectionAndLogExceptionsLocked(connection);
                 mAvailableNonPrimaryConnections.remove(i--);
                 count -= 1;
-                wake = true;
             }
         }
 
+        markAcquiredConnectionsLocked(AcquiredConnectionStatus.RECONFIGURE);
+    }
+
+    // Can't throw.
+    private void markAcquiredConnectionsLocked(AcquiredConnectionStatus status) {
         if (!mAcquiredConnections.isEmpty()) {
             ArrayList<SQLiteConnection> keysToUpdate = new ArrayList<SQLiteConnection>(
                     mAcquiredConnections.size());
-            for (Map.Entry<SQLiteConnection, Boolean> entry : mAcquiredConnections.entrySet()) {
-                if (entry.getValue() != Boolean.TRUE) {
+            for (Map.Entry<SQLiteConnection, AcquiredConnectionStatus> entry
+                    : mAcquiredConnections.entrySet()) {
+                AcquiredConnectionStatus oldStatus = entry.getValue();
+                if (status != oldStatus
+                        && oldStatus != AcquiredConnectionStatus.DISCARD) {
                     keysToUpdate.add(entry.getKey());
                 }
             }
             final int updateCount = keysToUpdate.size();
             for (int i = 0; i < updateCount; i++) {
-                mAcquiredConnections.put(keysToUpdate.get(i), Boolean.TRUE);
+                mAcquiredConnections.put(keysToUpdate.get(i), status);
             }
         }
-
-        if (wake) {
-            wakeConnectionWaitersLocked();
-        }
     }
 
     // Might throw.
@@ -658,8 +697,7 @@
         int activeConnections = 0;
         int idleConnections = 0;
         if (!mAcquiredConnections.isEmpty()) {
-            for (Map.Entry<SQLiteConnection, Boolean> entry : mAcquiredConnections.entrySet()) {
-                final SQLiteConnection connection = entry.getKey();
+            for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
                 String description = connection.describeCurrentOperationUnsafe();
                 if (description != null) {
                     requests.add(description);
@@ -769,7 +807,8 @@
 
         // Uhoh.  No primary connection!  Either this is the first time we asked
         // for it, or maybe it leaked?
-        connection = openConnectionLocked(true /*primaryConnection*/); // might throw
+        connection = openConnectionLocked(mConfiguration,
+                true /*primaryConnection*/); // might throw
         finishAcquireConnectionLocked(connection, connectionFlags); // might throw
         return connection;
     }
@@ -807,7 +846,8 @@
         if (openConnections >= mConfiguration.maxConnectionPoolSize) {
             return null;
         }
-        connection = openConnectionLocked(false /*primaryConnection*/); // might throw
+        connection = openConnectionLocked(mConfiguration,
+                false /*primaryConnection*/); // might throw
         finishAcquireConnectionLocked(connection, connectionFlags); // might throw
         return connection;
     }
@@ -818,7 +858,7 @@
             final boolean readOnly = (connectionFlags & CONNECTION_FLAG_READ_ONLY) != 0;
             connection.setOnlyAllowReadOnlyOperations(readOnly);
 
-            mAcquiredConnections.put(connection, Boolean.FALSE);
+            mAcquiredConnections.put(connection, AcquiredConnectionStatus.NORMAL);
         } catch (RuntimeException ex) {
             Log.e(TAG, "Failed to prepare acquired connection for session, closing it: "
                     + connection +", connectionFlags=" + connectionFlags);
@@ -858,7 +898,7 @@
     private void throwIfClosedLocked() {
         if (!mIsOpen) {
             throw new IllegalStateException("Cannot perform this operation "
-                    + "because the connection pool have been closed.");
+                    + "because the connection pool has been closed.");
         }
     }
 
@@ -922,11 +962,11 @@
 
             printer.println("  Acquired connections:");
             if (!mAcquiredConnections.isEmpty()) {
-                for (Map.Entry<SQLiteConnection, Boolean> entry :
+                for (Map.Entry<SQLiteConnection, AcquiredConnectionStatus> entry :
                         mAcquiredConnections.entrySet()) {
                     final SQLiteConnection connection = entry.getKey();
                     connection.dumpUnsafe(indentedPrinter, verbose);
-                    indentedPrinter.println("  Pending reconfiguration: " + entry.getValue());
+                    indentedPrinter.println("  Status: " + entry.getValue());
                 }
             } else {
                 indentedPrinter.println("<none>");
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 505f83e..515658f 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -677,6 +677,38 @@
         return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
     }
 
+    /**
+     * Reopens the database in read-write mode.
+     * If the database is already read-write, does nothing.
+     *
+     * @throws SQLiteException if the database could not be reopened as requested, in which
+     * case it remains open in read only mode.
+     * @throws IllegalStateException if the database is not open.
+     *
+     * @see #isReadOnly()
+     * @hide
+     */
+    public void reopenReadWrite() {
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+
+            if (!isReadOnlyLocked()) {
+                return; // nothing to do
+            }
+
+            // Reopen the database in read-write mode.
+            final int oldOpenFlags = mConfigurationLocked.openFlags;
+            mConfigurationLocked.openFlags = (mConfigurationLocked.openFlags & ~OPEN_READ_MASK)
+                    | OPEN_READWRITE;
+            try {
+                mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            } catch (RuntimeException ex) {
+                mConfigurationLocked.openFlags = oldOpenFlags;
+                throw ex;
+            }
+        }
+    }
+
     private void open() {
         try {
             try {
@@ -685,9 +717,6 @@
                 onCorruption();
                 openInner();
             }
-
-            // Disable WAL if it was previously enabled.
-            setJournalMode("TRUNCATE");
         } catch (SQLiteException ex) {
             Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);
             close();
@@ -707,19 +736,6 @@
         }
     }
 
-    private void setJournalMode(String mode) {
-        // Journal mode can be set only for non-memory databases
-        // AND can't be set for readonly databases
-        if (isInMemoryDatabase() || isReadOnly()) {
-            return;
-        }
-        String s = DatabaseUtils.stringForQuery(this, "PRAGMA journal_mode=" + mode, null);
-        if (!s.equalsIgnoreCase(mode)) {
-            Log.e(TAG, "setting journal_mode to " + mode + " failed for db: " + getLabel()
-                    + " (on pragma set journal_mode, sqlite returned:" + s);
-        }
-    }
-
     /**
      * Create a memory backed SQLite database.  Its contents will be destroyed
      * when the database is closed.
@@ -1729,13 +1745,10 @@
             }
 
             mIsWALEnabledLocked = true;
-            mConfigurationLocked.maxConnectionPoolSize = Math.max(2,
-                    Resources.getSystem().getInteger(
-                            com.android.internal.R.integer.db_connection_pool_size));
+            mConfigurationLocked.maxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
+            mConfigurationLocked.journalMode = "WAL";
             mConnectionPoolLocked.reconfigure(mConfigurationLocked);
         }
-
-        setJournalMode("WAL");
         return true;
     }
 
@@ -1753,10 +1766,9 @@
 
             mIsWALEnabledLocked = false;
             mConfigurationLocked.maxConnectionPoolSize = 1;
+            mConfigurationLocked.journalMode = SQLiteGlobal.getDefaultJournalMode();
             mConnectionPoolLocked.reconfigure(mConfigurationLocked);
         }
-
-        setJournalMode("TRUNCATE");
     }
 
     /**
@@ -1902,28 +1914,6 @@
         return true;
     }
 
-    /**
-     * Prevent other threads from using the database's primary connection.
-     *
-     * This method is only used by {@link SQLiteOpenHelper} when transitioning from
-     * a readable to a writable database.  It should not be used in any other way.
-     *
-     * @see #unlockPrimaryConnection()
-     */
-    void lockPrimaryConnection() {
-        getThreadSession().beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED,
-                null, SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY, null);
-    }
-
-    /**
-     * Allow other threads to use the database's primary connection.
-     *
-     * @see #lockPrimaryConnection()
-     */
-    void unlockPrimaryConnection() {
-        getThreadSession().endTransaction(null);
-    }
-
     @Override
     public String toString() {
         return "SQLiteDatabase: " + getPath();
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index bc79ad3..32a1bcb 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -51,17 +51,17 @@
     public final String path;
 
     /**
-     * The flags used to open the database.
-     */
-    public final int openFlags;
-
-    /**
      * The label to use to describe the database when it appears in logs.
      * This is derived from the path but is stripped to remove PII.
      */
     public final String label;
 
     /**
+     * The flags used to open the database.
+     */
+    public int openFlags;
+
+    /**
      * The maximum number of connections to retain in the connection pool.
      * Must be at least 1.
      *
@@ -85,6 +85,13 @@
     public Locale locale;
 
     /**
+     * The database journal mode.
+     *
+     * Default is {@link SQLiteGlobal#getDefaultJournalMode()}.
+     */
+    public String journalMode;
+
+    /**
      * The custom functions to register.
      */
     public final ArrayList<SQLiteCustomFunction> customFunctions =
@@ -103,13 +110,14 @@
         }
 
         this.path = path;
-        this.openFlags = openFlags;
         label = stripPathForLogs(path);
+        this.openFlags = openFlags;
 
         // Set default values for optional parameters.
         maxConnectionPoolSize = 1;
         maxSqlCacheSize = 25;
         locale = Locale.getDefault();
+        journalMode = SQLiteGlobal.getDefaultJournalMode();
     }
 
     /**
@@ -123,7 +131,6 @@
         }
 
         this.path = other.path;
-        this.openFlags = other.openFlags;
         this.label = other.label;
         updateParametersFrom(other);
     }
@@ -138,14 +145,16 @@
         if (other == null) {
             throw new IllegalArgumentException("other must not be null.");
         }
-        if (!path.equals(other.path) || openFlags != other.openFlags) {
+        if (!path.equals(other.path)) {
             throw new IllegalArgumentException("other configuration must refer to "
                     + "the same database.");
         }
 
+        openFlags = other.openFlags;
         maxConnectionPoolSize = other.maxConnectionPoolSize;
         maxSqlCacheSize = other.maxSqlCacheSize;
         locale = other.locale;
+        journalMode = other.journalMode;
         customFunctions.clear();
         customFunctions.addAll(other.customFunctions);
     }
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index dbefd63..af0cf45 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.content.res.Resources;
 import android.os.StatFs;
 
 /**
@@ -64,4 +65,44 @@
             return sDefaultPageSize;
         }
     }
+
+    /**
+     * Gets the default journal mode when WAL is not in use.
+     */
+    public static String getDefaultJournalMode() {
+        return Resources.getSystem().getString(
+                com.android.internal.R.string.db_default_journal_mode);
+    }
+
+    /**
+     * Gets the journal size limit in bytes.
+     */
+    public static int getJournalSizeLimit() {
+        return Resources.getSystem().getInteger(
+                com.android.internal.R.integer.db_journal_size_limit);
+    }
+
+    /**
+     * Gets the database synchronization mode.
+     */
+    public static String getSyncMode() {
+        return Resources.getSystem().getString(
+                com.android.internal.R.string.db_sync_mode);
+    }
+
+    /**
+     * Gets the WAL auto-checkpoint integer in database pages.
+     */
+    public static int getWALAutoCheckpoint() {
+        return Math.max(1, Resources.getSystem().getInteger(
+                com.android.internal.R.integer.db_wal_autocheckpoint));
+    }
+
+    /**
+     * Gets the default connection pool size when in WAL mode.
+     */
+    public static int getWALConnectionPoolSize() {
+        return Math.max(2, Resources.getSystem().getInteger(
+                com.android.internal.R.integer.db_connection_pool_size));
+    }
 }
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 46d9369..ffa4663 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -43,13 +43,21 @@
 public abstract class SQLiteOpenHelper {
     private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
 
+    // When true, getReadableDatabase returns a read-only database if it is just being opened.
+    // The database handle is reopened in read/write mode when getWritableDatabase is called.
+    // We leave this behavior disabled in production because it is inefficient and breaks
+    // many applications.  For debugging purposes it can be useful to turn on strict
+    // read-only semantics to catch applications that call getReadableDatabase when they really
+    // wanted getWritableDatabase.
+    private static final boolean DEBUG_STRICT_READONLY = false;
+
     private final Context mContext;
     private final String mName;
     private final CursorFactory mFactory;
     private final int mNewVersion;
 
-    private SQLiteDatabase mDatabase = null;
-    private boolean mIsInitializing = false;
+    private SQLiteDatabase mDatabase;
+    private boolean mIsInitializing;
     private final DatabaseErrorHandler mErrorHandler;
 
     /**
@@ -127,76 +135,9 @@
      * @throws SQLiteException if the database cannot be opened for writing
      * @return a read/write database object valid until {@link #close} is called
      */
-    public synchronized SQLiteDatabase getWritableDatabase() {
-        if (mDatabase != null) {
-            if (!mDatabase.isOpen()) {
-                // darn! the user closed the database by calling mDatabase.close()
-                mDatabase = null;
-            } else if (!mDatabase.isReadOnly()) {
-                return mDatabase;  // The database is already open for business
-            }
-        }
-
-        if (mIsInitializing) {
-            throw new IllegalStateException("getWritableDatabase called recursively");
-        }
-
-        // If we have a read-only database open, someone could be using it
-        // (though they shouldn't), which would cause a lock to be held on
-        // the file, and our attempts to open the database read-write would
-        // fail waiting for the file lock.  To prevent that, we acquire a lock
-        // on the read-only database, which shuts out other users.
-
-        boolean success = false;
-        SQLiteDatabase db = null;
-        if (mDatabase != null) {
-            mDatabase.lockPrimaryConnection();
-        }
-        try {
-            mIsInitializing = true;
-            if (mName == null) {
-                db = SQLiteDatabase.create(null);
-            } else {
-                db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler);
-            }
-
-            int version = db.getVersion();
-            if (version != mNewVersion) {
-                db.beginTransaction();
-                try {
-                    if (version == 0) {
-                        onCreate(db);
-                    } else {
-                        if (version > mNewVersion) {
-                            onDowngrade(db, version, mNewVersion);
-                        } else {
-                            onUpgrade(db, version, mNewVersion);
-                        }
-                    }
-                    db.setVersion(mNewVersion);
-                    db.setTransactionSuccessful();
-                } finally {
-                    db.endTransaction();
-                }
-            }
-
-            onOpen(db);
-            success = true;
-            return db;
-        } finally {
-            mIsInitializing = false;
-            if (success) {
-                if (mDatabase != null) {
-                    try { mDatabase.close(); } catch (Exception e) { }
-                    mDatabase.unlockPrimaryConnection();
-                }
-                mDatabase = db;
-            } else {
-                if (mDatabase != null) {
-                    mDatabase.unlockPrimaryConnection();
-                }
-                if (db != null) db.close();
-            }
+    public SQLiteDatabase getWritableDatabase() {
+        synchronized (this) {
+            return getDatabaseLocked(true);
         }
     }
 
@@ -218,45 +159,95 @@
      * @return a database object valid until {@link #getWritableDatabase}
      *     or {@link #close} is called.
      */
-    public synchronized SQLiteDatabase getReadableDatabase() {
+    public SQLiteDatabase getReadableDatabase() {
+        synchronized (this) {
+            return getDatabaseLocked(false);
+        }
+    }
+
+    private SQLiteDatabase getDatabaseLocked(boolean writable) {
         if (mDatabase != null) {
             if (!mDatabase.isOpen()) {
-                // darn! the user closed the database by calling mDatabase.close()
+                // Darn!  The user closed the database by calling mDatabase.close().
                 mDatabase = null;
-            } else {
-                return mDatabase;  // The database is already open for business
+            } else if (!writable || !mDatabase.isReadOnly()) {
+                // The database is already open for business.
+                return mDatabase;
             }
         }
 
         if (mIsInitializing) {
-            throw new IllegalStateException("getReadableDatabase called recursively");
+            throw new IllegalStateException("getDatabase called recursively");
         }
 
-        try {
-            return getWritableDatabase();
-        } catch (SQLiteException e) {
-            if (mName == null) throw e;  // Can't open a temp database read-only!
-            Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
-        }
-
-        SQLiteDatabase db = null;
+        SQLiteDatabase db = mDatabase;
         try {
             mIsInitializing = true;
-            String path = mContext.getDatabasePath(mName).getPath();
-            db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY,
-                    mErrorHandler);
-            if (db.getVersion() != mNewVersion) {
-                throw new SQLiteException("Can't upgrade read-only database from version " +
-                        db.getVersion() + " to " + mNewVersion + ": " + path);
+
+            if (db != null) {
+                if (writable && db.isReadOnly()) {
+                    db.reopenReadWrite();
+                }
+            } else if (mName == null) {
+                db = SQLiteDatabase.create(null);
+            } else {
+                try {
+                    if (DEBUG_STRICT_READONLY && !writable) {
+                        final String path = mContext.getDatabasePath(mName).getPath();
+                        db = SQLiteDatabase.openDatabase(path, mFactory,
+                                SQLiteDatabase.OPEN_READONLY, mErrorHandler);
+                    } else {
+                        db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler);
+                    }
+                } catch (SQLiteException ex) {
+                    if (writable) {
+                        throw ex;
+                    }
+                    Log.e(TAG, "Couldn't open " + mName
+                            + " for writing (will try read-only):", ex);
+                    final String path = mContext.getDatabasePath(mName).getPath();
+                    db = SQLiteDatabase.openDatabase(path, mFactory,
+                            SQLiteDatabase.OPEN_READONLY, mErrorHandler);
+                }
             }
 
+            final int version = db.getVersion();
+            if (version != mNewVersion) {
+                if (db.isReadOnly()) {
+                    throw new SQLiteException("Can't upgrade read-only database from version " +
+                            db.getVersion() + " to " + mNewVersion + ": " + mName);
+                }
+
+                db.beginTransaction();
+                try {
+                    if (version == 0) {
+                        onCreate(db);
+                    } else {
+                        if (version > mNewVersion) {
+                            onDowngrade(db, version, mNewVersion);
+                        } else {
+                            onUpgrade(db, version, mNewVersion);
+                        }
+                    }
+                    db.setVersion(mNewVersion);
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
             onOpen(db);
-            Log.w(TAG, "Opened " + mName + " in read-only mode");
+
+            if (db.isReadOnly()) {
+                Log.w(TAG, "Opened " + mName + " in read-only mode");
+            }
+
             mDatabase = db;
-            return mDatabase;
+            return db;
         } finally {
             mIsInitializing = false;
-            if (db != null && db != mDatabase) db.close();
+            if (db != null && db != mDatabase) {
+                db.close();
+            }
         }
     }
 
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 2f43cb8..0bc6b58 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -77,7 +77,9 @@
         /** Attempt to connect failed. */
         FAILED,
         /** Access to this network is blocked. */
-        BLOCKED
+        BLOCKED,
+        /** Link has poor connectivity. */
+        VERIFYING_POOR_LINK
     }
 
     /**
@@ -94,6 +96,7 @@
         stateMap.put(DetailedState.CONNECTING, State.CONNECTING);
         stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING);
         stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING);
+        stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING);
         stateMap.put(DetailedState.CONNECTED, State.CONNECTED);
         stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED);
         stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
diff --git a/core/java/android/net/arp/ArpPeer.java b/core/java/android/net/arp/ArpPeer.java
new file mode 100644
index 0000000..8e666bc
--- /dev/null
+++ b/core/java/android/net/arp/ArpPeer.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2012 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.arp;
+
+import android.os.SystemClock;
+import android.util.Log;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Inet6Address;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+import libcore.net.RawSocket;
+
+/**
+ * This class allows simple ARP exchanges over an uninitialized network
+ * interface.
+ *
+ * @hide
+ */
+public class ArpPeer {
+    private String mInterfaceName;
+    private final InetAddress mMyAddr;
+    private final byte[] mMyMac = new byte[6];
+    private final InetAddress mPeer;
+    private final RawSocket mSocket;
+    private final byte[] L2_BROADCAST;  // TODO: refactor from DhcpClient.java
+    private static final int MAX_LENGTH = 1500; // refactor from DhcpPacket.java
+    private static final int ETHERNET_TYPE = 1;
+    private static final int ARP_LENGTH = 28;
+    private static final int MAC_ADDR_LENGTH = 6;
+    private static final int IPV4_LENGTH = 4;
+    private static final String TAG = "ArpPeer";
+
+    public ArpPeer(String interfaceName, InetAddress myAddr, String mac,
+                   InetAddress peer) throws SocketException {
+        mInterfaceName = interfaceName;
+        mMyAddr = myAddr;
+
+        for (int i = 0; i < MAC_ADDR_LENGTH; i++) {
+            mMyMac[i] = (byte) Integer.parseInt(mac.substring(
+                        i*3, (i*3) + 2), 16);
+        }
+
+        if (myAddr instanceof Inet6Address || peer instanceof Inet6Address) {
+            throw new IllegalArgumentException("IPv6 unsupported");
+        }
+
+        mPeer = peer;
+        L2_BROADCAST = new byte[MAC_ADDR_LENGTH];
+        Arrays.fill(L2_BROADCAST, (byte) 0xFF);
+
+        mSocket = new RawSocket(mInterfaceName, RawSocket.ETH_P_ARP);
+    }
+
+    /**
+     * Returns the MAC address (or null if timeout) for the requested
+     * peer.
+     */
+    public byte[] doArp(int timeoutMillis) {
+        ByteBuffer buf = ByteBuffer.allocate(MAX_LENGTH);
+        byte[] desiredIp = mPeer.getAddress();
+        long timeout = SystemClock.elapsedRealtime() + timeoutMillis;
+
+        // construct ARP request packet, using a ByteBuffer as a
+        // convenient container
+        buf.clear();
+        buf.order(ByteOrder.BIG_ENDIAN);
+
+        buf.putShort((short) ETHERNET_TYPE); // Ethernet type, 16 bits
+        buf.putShort(RawSocket.ETH_P_IP); // Protocol type IP, 16 bits
+        buf.put((byte)MAC_ADDR_LENGTH);  // MAC address length, 6 bytes
+        buf.put((byte)IPV4_LENGTH);  // IPv4 protocol size
+        buf.putShort((short) 1); // ARP opcode 1: 'request'
+        buf.put(mMyMac);        // six bytes: sender MAC
+        buf.put(mMyAddr.getAddress());  // four bytes: sender IP address
+        buf.put(new byte[MAC_ADDR_LENGTH]); // target MAC address: unknown
+        buf.put(desiredIp); // target IP address, 4 bytes
+        buf.flip();
+        mSocket.write(L2_BROADCAST, buf.array(), 0, buf.limit());
+
+        byte[] recvBuf = new byte[MAX_LENGTH];
+
+        while (SystemClock.elapsedRealtime() < timeout) {
+            long duration = (long) timeout - SystemClock.elapsedRealtime();
+            int readLen = mSocket.read(recvBuf, 0, recvBuf.length, -1,
+                (int) duration);
+
+            // Verify packet details. see RFC 826
+            if ((readLen >= ARP_LENGTH) // trailing bytes at times
+                && (recvBuf[0] == 0) && (recvBuf[1] == ETHERNET_TYPE) // type Ethernet
+                && (recvBuf[2] == 8) && (recvBuf[3] == 0) // protocol IP
+                && (recvBuf[4] == MAC_ADDR_LENGTH) // mac length
+                && (recvBuf[5] == IPV4_LENGTH) // IPv4 protocol size
+                && (recvBuf[6] == 0) && (recvBuf[7] == 2) // ARP reply
+                // verify desired IP address
+                && (recvBuf[14] == desiredIp[0]) && (recvBuf[15] == desiredIp[1])
+                && (recvBuf[16] == desiredIp[2]) && (recvBuf[17] == desiredIp[3]))
+            {
+                // looks good.  copy out the MAC
+                byte[] result = new byte[MAC_ADDR_LENGTH];
+                System.arraycopy(recvBuf, 8, result, 0, MAC_ADDR_LENGTH);
+                return result;
+            }
+        }
+
+        return null;
+    }
+
+    public void close() {
+        try {
+            mSocket.close();
+        } catch (IOException ex) {
+        }
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0aad64a..b42417a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3125,15 +3125,8 @@
          * ms delay before rechecking an 'online' wifi connection when it is thought to be unstable.
          * @hide
          */
-        public static final String WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS =
-                "wifi_watchdog_dns_check_short_interval_ms";
-
-        /**
-         * ms delay before rechecking an 'online' wifi connection when it is thought to be stable.
-         * @hide
-         */
-        public static final String WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS =
-                "wifi_watchdog_dns_check_long_interval_ms";
+        public static final String WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS =
+                "wifi_watchdog_arp_interval_ms";
 
         /**
          * ms delay before rechecking a connect SSID for walled garden with a http download.
@@ -3143,44 +3136,28 @@
                 "wifi_watchdog_walled_garden_interval_ms";
 
         /**
-         * max blacklist calls on an SSID before full dns check failures disable the network.
+         * Number of ARP pings per check.
          * @hide
          */
-        public static final String WIFI_WATCHDOG_MAX_SSID_BLACKLISTS =
-                "wifi_watchdog_max_ssid_blacklists";
+        public static final String WIFI_WATCHDOG_NUM_ARP_PINGS = "wifi_watchdog_num_arp_pings";
 
         /**
-         * Number of dns pings per check.
+         * Minimum number of responses to the arp pings to consider the test 'successful'.
          * @hide
          */
-        public static final String WIFI_WATCHDOG_NUM_DNS_PINGS = "wifi_watchdog_num_dns_pings";
+        public static final String WIFI_WATCHDOG_MIN_ARP_RESPONSES =
+                "wifi_watchdog_min_arp_responses";
 
         /**
-         * Minimum number of responses to the dns pings to consider the test 'successful'.
+         * Timeout on ARP pings
          * @hide
          */
-        public static final String WIFI_WATCHDOG_MIN_DNS_RESPONSES =
-                "wifi_watchdog_min_dns_responses";
+        public static final String WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS =
+                "wifi_watchdog_arp_ping_timeout_ms";
 
         /**
-         * Timeout on dns pings
-         * @hide
-         */
-        public static final String WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS =
-                "wifi_watchdog_dns_ping_timeout_ms";
-
-        /**
-         * We consider action from a 'blacklist' call to have finished by the end of
-         * this interval.  If we are connected to the same AP with no network connection,
-         * we are likely stuck on an SSID with no external connectivity.
-         * @hide
-         */
-        public static final String WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS =
-                "wifi_watchdog_blacklist_followup_interval_ms";
-
-        /**
-         * Setting to turn off poor network avoidance on Wi-Fi. Feature is disabled by default and
-         * the setting needs to be set to 1 to enable it.
+         * Setting to turn off poor network avoidance on Wi-Fi. Feature is enabled by default and
+         * the setting needs to be set to 0 to disable it.
          * @hide
          */
         public static final String WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED =
@@ -3204,14 +3181,6 @@
                 "wifi_watchdog_walled_garden_url";
 
         /**
-         * Boolean to determine whether to notify on disabling a network.  Secure setting used
-         * to notify user only once.
-         * @hide
-         */
-        public static final String WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP =
-                "wifi_watchdog_show_disabled_network_popup";
-
-        /**
          * The maximum number of times we will retry a connection to an access
          * point for which we have failed in acquiring an IP address from DHCP.
          * A value of N means that we will make N+1 connection attempts in all.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 1dd4c8a..299e115 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -716,7 +716,8 @@
             boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
             boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
 
-            boolean doEllipsis = (firstLine && !moreChars &&
+            boolean doEllipsis =
+                        (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
                                 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
                         (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
                                 ellipsize == TextUtils.TruncateAt.END);
diff --git a/core/java/android/view/textservice/SentenceSuggestionsInfo.java b/core/java/android/view/textservice/SentenceSuggestionsInfo.java
index 8d7c6cf..cb9e496 100644
--- a/core/java/android/view/textservice/SentenceSuggestionsInfo.java
+++ b/core/java/android/view/textservice/SentenceSuggestionsInfo.java
@@ -84,6 +84,13 @@
     /**
      * @hide
      */
+    public int getSuggestionsCount() {
+        return mSuggestionsInfos.length;
+    }
+
+    /**
+     * @hide
+     */
     public SuggestionsInfo getSuggestionsInfoAt(int i) {
         if (i >= 0 && i < mSuggestionsInfos.length) {
             return mSuggestionsInfos[i];
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 3491a53..9105f19 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -180,9 +180,9 @@
     /**
      * @hide
      */
-    public void getSentenceSuggestions(TextInfo textInfo, int suggestionsLimit) {
+    public void getSentenceSuggestions(TextInfo[] textInfo, int suggestionsLimit) {
         mSpellCheckerSessionListenerImpl.getSentenceSuggestionsMultiple(
-                new TextInfo[] {textInfo}, suggestionsLimit);
+                textInfo, suggestionsLimit);
     }
 
     /**
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index a1cf205..df2996c 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -57,9 +57,14 @@
     // Pause between each spell check to keep the UI smooth
     private final static int SPELL_PAUSE_DURATION = 400; // milliseconds
 
+    private static final int USE_SPAN_RANGE = -1;
+
     private final TextView mTextView;
 
     SpellCheckerSession mSpellCheckerSession;
+    // We assume that the sentence level spell check will always provide better results than words.
+    // Although word SC has a sequential option.
+    private boolean mIsSentenceSpellCheckSupported;
     final int mCookie;
 
     // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
@@ -111,6 +116,7 @@
                     null /* Bundle not currently used by the textServicesManager */,
                     mCurrentLocale, this,
                     false /* means any available languages from current spell checker */);
+            mIsSentenceSpellCheckSupported = mSpellCheckerSession.isSentenceSpellCheckSupported();
         }
 
         // Restore SpellCheckSpans in pool
@@ -272,46 +278,80 @@
                 textInfos = textInfosCopy;
             }
 
-            mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
-                    false /* TODO Set sequentialWords to true for initial spell check */);
+            if (mIsSentenceSpellCheckSupported) {
+                mSpellCheckerSession.getSentenceSuggestions(
+                        textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
+            } else {
+                mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
+                        false /* TODO Set sequentialWords to true for initial spell check */);
+            }
         }
     }
 
-    @Override
-    public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
-        // TODO: Handle the position and length for each suggestion
-        // do nothing for now
+    private SpellCheckSpan onGetSuggestionsInternal(
+            SuggestionsInfo suggestionsInfo, int offset, int length) {
+        if (suggestionsInfo.getCookie() != mCookie) {
+            return null;
+        }
+        final Editable editable = (Editable) mTextView.getText();
+        final int sequenceNumber = suggestionsInfo.getSequence();
+        for (int k = 0; k < mLength; ++k) {
+            if (sequenceNumber == mIds[k]) {
+                final int attributes = suggestionsInfo.getSuggestionsAttributes();
+                final boolean isInDictionary =
+                        ((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0);
+                final boolean looksLikeTypo =
+                        ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0);
+
+                final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[k];
+                //TODO: we need to change that rule for results from a sentence-level spell
+                // checker that will probably be in dictionary.
+                if (!isInDictionary && looksLikeTypo) {
+                    createMisspelledSuggestionSpan(
+                            editable, suggestionsInfo, spellCheckSpan, offset, length);
+                }
+                return spellCheckSpan;
+            }
+        }
+        return null;
     }
 
     @Override
     public void onGetSuggestions(SuggestionsInfo[] results) {
-        Editable editable = (Editable) mTextView.getText();
-
-        for (int i = 0; i < results.length; i++) {
-            SuggestionsInfo suggestionsInfo = results[i];
-            if (suggestionsInfo.getCookie() != mCookie) continue;
-            final int sequenceNumber = suggestionsInfo.getSequence();
-
-            for (int j = 0; j < mLength; j++) {
-                if (sequenceNumber == mIds[j]) {
-                    final int attributes = suggestionsInfo.getSuggestionsAttributes();
-                    boolean isInDictionary =
-                            ((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0);
-                    boolean looksLikeTypo =
-                            ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0);
-
-                    SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];
-
-                    if (!isInDictionary && looksLikeTypo) {
-                        createMisspelledSuggestionSpan(editable, suggestionsInfo, spellCheckSpan);
-                    }
-
-                    editable.removeSpan(spellCheckSpan);
-                    break;
-                }
+        final Editable editable = (Editable) mTextView.getText();
+        for (int i = 0; i < results.length; ++i) {
+            final SpellCheckSpan spellCheckSpan =
+                    onGetSuggestionsInternal(results[i], USE_SPAN_RANGE, USE_SPAN_RANGE);
+            if (spellCheckSpan != null) {
+                editable.removeSpan(spellCheckSpan);
             }
         }
+        scheduleNewSpellCheck();
+    }
 
+    @Override
+    public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
+        final Editable editable = (Editable) mTextView.getText();
+
+        for (int i = 0; i < results.length; ++i) {
+            final SentenceSuggestionsInfo ssi = results[i];
+            SpellCheckSpan spellCheckSpan = null;
+            for (int j = 0; j < ssi.getSuggestionsCount(); ++j) {
+                final SuggestionsInfo suggestionsInfo = ssi.getSuggestionsInfoAt(j);
+                final int offset = ssi.getOffsetAt(j);
+                final int length = ssi.getLengthAt(j);
+                final SpellCheckSpan scs = onGetSuggestionsInternal(
+                        suggestionsInfo, offset, length);
+                if (spellCheckSpan == null && scs != null) {
+                    // the spellCheckSpan is shared by all the "SuggestionsInfo"s in the same
+                    // SentenceSuggestionsInfo
+                    spellCheckSpan = scs;
+                }
+            }
+            if (spellCheckSpan != null) {
+                editable.removeSpan(spellCheckSpan);
+            }
+        }
         scheduleNewSpellCheck();
     }
 
@@ -338,10 +378,11 @@
     }
 
     private void createMisspelledSuggestionSpan(Editable editable, SuggestionsInfo suggestionsInfo,
-            SpellCheckSpan spellCheckSpan) {
-        final int start = editable.getSpanStart(spellCheckSpan);
-        final int end = editable.getSpanEnd(spellCheckSpan);
-        if (start < 0 || end <= start) return; // span was removed in the meantime
+            SpellCheckSpan spellCheckSpan, int offset, int length) {
+        final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan);
+        final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan);
+        if (spellCheckSpanStart < 0 || spellCheckSpanEnd <= spellCheckSpanStart)
+            return; // span was removed in the meantime
 
         final int suggestionsCount = suggestionsInfo.getSuggestionsCount();
         if (suggestionsCount <= 0) {
@@ -349,6 +390,16 @@
             return;
         }
 
+        final int start;
+        final int end;
+        if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) {
+            start = spellCheckSpanStart + offset;
+            end = start + length;
+        } else {
+            start = spellCheckSpanStart;
+            end = spellCheckSpanEnd;
+        }
+
         String[] suggestions = new String[suggestionsCount];
         for (int i = 0; i < suggestionsCount; i++) {
             suggestions[i] = suggestionsInfo.getSuggestionAt(i);
@@ -419,67 +470,97 @@
             int wordCount = 0;
             boolean scheduleOtherSpellCheck = false;
 
-            while (wordStart <= end) {
-                if (wordEnd >= start && wordEnd > wordStart) {
-                    if (wordCount >= MAX_NUMBER_OF_WORDS) {
-                        scheduleOtherSpellCheck = true;
+            if (mIsSentenceSpellCheckSupported) {
+                int regionEnd;
+                if (wordIteratorWindowEnd < end) {
+                    // Several batches needed on that region. Cut after last previous word
+                    regionEnd = mWordIterator.preceding(wordIteratorWindowEnd);
+                    scheduleOtherSpellCheck = true;
+                } else {
+                    regionEnd = mWordIterator.preceding(end);
+                }
+                boolean correct = regionEnd != BreakIterator.DONE;
+                if (correct) {
+                    regionEnd = mWordIterator.getEnd(regionEnd);
+                    correct = regionEnd != BreakIterator.DONE;
+                }
+                if (!correct) {
+                    editable.removeSpan(mRange);
+                    return;
+                }
+                wordStart = regionEnd;
+                // TODO: Find the start position of the sentence.
+                // Set span with the context
+                final int spellCheckStart =  Math.min(
+                        start, Math.max(wordStart, regionEnd - WORD_ITERATOR_INTERVAL));
+                if (regionEnd <= spellCheckStart) {
+                    return;
+                }
+                addSpellCheckSpan(editable, spellCheckStart, regionEnd);
+            } else {
+                while (wordStart <= end) {
+                    if (wordEnd >= start && wordEnd > wordStart) {
+                        if (wordCount >= MAX_NUMBER_OF_WORDS) {
+                            scheduleOtherSpellCheck = true;
+                            break;
+                        }
+                        // A new word has been created across the interval boundaries with this
+                        // edit. The previous spans (that ended on start / started on end) are
+                        // not valid anymore and must be removed.
+                        if (wordStart < start && wordEnd > start) {
+                            removeSpansAt(editable, start, spellCheckSpans);
+                            removeSpansAt(editable, start, suggestionSpans);
+                        }
+
+                        if (wordStart < end && wordEnd > end) {
+                            removeSpansAt(editable, end, spellCheckSpans);
+                            removeSpansAt(editable, end, suggestionSpans);
+                        }
+
+                        // Do not create new boundary spans if they already exist
+                        boolean createSpellCheckSpan = true;
+                        if (wordEnd == start) {
+                            for (int i = 0; i < spellCheckSpans.length; i++) {
+                                final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]);
+                                if (spanEnd == start) {
+                                    createSpellCheckSpan = false;
+                                    break;
+                                }
+                            }
+                        }
+
+                        if (wordStart == end) {
+                            for (int i = 0; i < spellCheckSpans.length; i++) {
+                                final int spanStart = editable.getSpanStart(spellCheckSpans[i]);
+                                if (spanStart == end) {
+                                    createSpellCheckSpan = false;
+                                    break;
+                                }
+                            }
+                        }
+
+                        if (createSpellCheckSpan) {
+                            addSpellCheckSpan(editable, wordStart, wordEnd);
+                        }
+                        wordCount++;
+                    }
+
+                    // iterate word by word
+                    int originalWordEnd = wordEnd;
+                    wordEnd = mWordIterator.following(wordEnd);
+                    if ((wordIteratorWindowEnd < end) &&
+                            (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) {
+                        wordIteratorWindowEnd =
+                                Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL);
+                        mWordIterator.setCharSequence(
+                                editable, originalWordEnd, wordIteratorWindowEnd);
+                        wordEnd = mWordIterator.following(originalWordEnd);
+                    }
+                    if (wordEnd == BreakIterator.DONE) break;
+                    wordStart = mWordIterator.getBeginning(wordEnd);
+                    if (wordStart == BreakIterator.DONE) {
                         break;
                     }
-
-                    // A new word has been created across the interval boundaries with this edit.
-                    // The previous spans (that ended on start / started on end) are not valid
-                    // anymore and must be removed.
-                    if (wordStart < start && wordEnd > start) {
-                        removeSpansAt(editable, start, spellCheckSpans);
-                        removeSpansAt(editable, start, suggestionSpans);
-                    }
-
-                    if (wordStart < end && wordEnd > end) {
-                        removeSpansAt(editable, end, spellCheckSpans);
-                        removeSpansAt(editable, end, suggestionSpans);
-                    }
-
-                    // Do not create new boundary spans if they already exist
-                    boolean createSpellCheckSpan = true;
-                    if (wordEnd == start) {
-                        for (int i = 0; i < spellCheckSpans.length; i++) {
-                            final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]);
-                            if (spanEnd == start) {
-                                createSpellCheckSpan = false;
-                                break;
-                            }
-                        }
-                    }
-
-                    if (wordStart == end) {
-                        for (int i = 0; i < spellCheckSpans.length; i++) {
-                            final int spanStart = editable.getSpanStart(spellCheckSpans[i]);
-                            if (spanStart == end) {
-                                createSpellCheckSpan = false;
-                                break;
-                            }
-                        }
-                    }
-
-                    if (createSpellCheckSpan) {
-                        addSpellCheckSpan(editable, wordStart, wordEnd);
-                    }
-                    wordCount++;
-                }
-
-                // iterate word by word
-                int originalWordEnd = wordEnd;
-                wordEnd = mWordIterator.following(wordEnd);
-                if ((wordIteratorWindowEnd < end) &&
-                        (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) {
-                    wordIteratorWindowEnd = Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL);
-                    mWordIterator.setCharSequence(editable, originalWordEnd, wordIteratorWindowEnd);
-                    wordEnd = mWordIterator.following(originalWordEnd);
-                }
-                if (wordEnd == BreakIterator.DONE) break;
-                wordStart = mWordIterator.getBeginning(wordEnd);
-                if (wordStart == BreakIterator.DONE) {
-                    break;
                 }
             }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 29e9e91..385c7c7 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5996,7 +5996,6 @@
                 if (des < 0) {
                     des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
                 }
-
                 width = des;
             } else {
                 width = boring.width;
@@ -6017,7 +6016,7 @@
                 }
 
                 if (hintDes < 0) {
-                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
+                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
                     if (hintBoring != null) {
                         mHintBoring = hintBoring;
                     }
@@ -6025,10 +6024,8 @@
 
                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
                     if (hintDes < 0) {
-                        hintDes = (int) FloatMath.ceil(
-                                Layout.getDesiredWidth(mHint, mTextPaint));
+                        hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
                     }
-
                     hintWidth = hintDes;
                 } else {
                     hintWidth = hintBoring.width;
@@ -6292,20 +6289,25 @@
         if (changed && mEditor != null) getEditor().mTextDisplayListIsValid = false;
     }
 
+    private boolean isShowingHint() {
+        return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
+    }
+
     /**
      * Returns true if anything changed.
      */
     private boolean bringTextIntoView() {
+        Layout layout = isShowingHint() ? mHintLayout : mLayout;
         int line = 0;
         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
-            line = mLayout.getLineCount() - 1;
+            line = layout.getLineCount() - 1;
         }
 
-        Layout.Alignment a = mLayout.getParagraphAlignment(line);
-        int dir = mLayout.getParagraphDirection(line);
+        Layout.Alignment a = layout.getParagraphAlignment(line);
+        int dir = layout.getParagraphDirection(line);
         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
-        int ht = mLayout.getHeight();
+        int ht = layout.getHeight();
 
         int scrollx, scrolly;
 
@@ -6324,8 +6326,8 @@
              * keep leading edge in view.
              */
 
-            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
-            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
+            int left = (int) FloatMath.floor(layout.getLineLeft(line));
+            int right = (int) FloatMath.ceil(layout.getLineRight(line));
 
             if (right - left < hspace) {
                 scrollx = (right + left) / 2 - hspace / 2;
@@ -6337,10 +6339,10 @@
                 }
             }
         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
-            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
+            int right = (int) FloatMath.ceil(layout.getLineRight(line));
             scrollx = right - hspace;
         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
-            scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
+            scrollx = (int) FloatMath.floor(layout.getLineLeft(line));
         }
 
         if (ht < vspace) {
@@ -6368,22 +6370,24 @@
     public boolean bringPointIntoView(int offset) {
         boolean changed = false;
 
-        if (mLayout == null) return changed;
+        Layout layout = isShowingHint() ? mHintLayout: mLayout;
 
-        int line = mLayout.getLineForOffset(offset);
+        if (layout == null) return changed;
+
+        int line = layout.getLineForOffset(offset);
 
         // FIXME: Is it okay to truncate this, or should we round?
-        final int x = (int)mLayout.getPrimaryHorizontal(offset);
-        final int top = mLayout.getLineTop(line);
-        final int bottom = mLayout.getLineTop(line + 1);
+        final int x = (int)layout.getPrimaryHorizontal(offset);
+        final int top = layout.getLineTop(line);
+        final int bottom = layout.getLineTop(line + 1);
 
-        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
-        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
-        int ht = mLayout.getHeight();
+        int left = (int) FloatMath.floor(layout.getLineLeft(line));
+        int right = (int) FloatMath.ceil(layout.getLineRight(line));
+        int ht = layout.getHeight();
 
         int grav;
 
-        switch (mLayout.getParagraphAlignment(line)) {
+        switch (layout.getParagraphAlignment(line)) {
             case ALIGN_LEFT:
                 grav = 1;
                 break;
@@ -6391,10 +6395,10 @@
                 grav = -1;
                 break;
             case ALIGN_NORMAL:
-                grav = mLayout.getParagraphDirection(line);
+                grav = layout.getParagraphDirection(line);
                 break;
             case ALIGN_OPPOSITE:
-                grav = -mLayout.getParagraphDirection(line);
+                grav = -layout.getParagraphDirection(line);
                 break;
             case ALIGN_CENTER:
             default:
@@ -8276,8 +8280,11 @@
     @Override
     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
-        if (mEditor != null && getEditor().mPositionListener != null) {
-            getEditor().mPositionListener.onScrollChanged();
+        if (mEditor != null) {
+            if (getEditor().mPositionListener != null) {
+                getEditor().mPositionListener.onScrollChanged();
+            }
+            getEditor().mTextDisplayListIsValid = false;
         }
     }
 
@@ -8710,6 +8717,8 @@
     @Override
     public void onResolveTextDirection() {
         if (hasPasswordTransformationMethod()) {
+            // TODO: take care of the content direction to show the password text and dots justified
+            // to the left or to the right
             mTextDir = TextDirectionHeuristics.LOCALE;
             return;
         }
@@ -10684,9 +10693,12 @@
                 return;
             }
 
-            if (offset != mPreviousOffset || parentScrolled) {
-                updateSelection(offset);
-                addPositionToTouchUpFilter(offset);
+            boolean offsetChanged = offset != mPreviousOffset;
+            if (offsetChanged || parentScrolled) {
+                if (offsetChanged) {
+                    updateSelection(offset);
+                    addPositionToTouchUpFilter(offset);
+                }
                 final int line = mLayout.getLineForOffset(offset);
 
                 mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index 80ecf2f..c8f911f 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -128,15 +128,6 @@
         return 0;
     }
 
-    // Enable WAL auto-checkpointing after a commit whenever at least one frame is in the log.
-    // This ensures that a checkpoint will occur after each transaction if needed.
-    err = sqlite3_wal_autocheckpoint(db, 1);
-    if (err) {
-        throw_sqlite3_exception(env, db, "Could not enable auto-checkpointing.");
-        sqlite3_close(db);
-        return 0;
-    }
-
     // Register custom Android functions.
     err = register_android_functions(db, UTF16_STORAGE);
     if (err) {
@@ -426,11 +417,11 @@
     SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
     sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
 
+    // We ignore the result of sqlite3_finalize because it is really telling us about
+    // whether any errors occurred while executing the statement.  The statement itself
+    // is always finalized regardless.
     ALOGV("Finalized statement %p on connection %p", statement, connection->db);
-    int err = sqlite3_finalize(statement);
-    if (err != SQLITE_OK) {
-        throw_sqlite3_exception(env, connection->db, NULL);
-    }
+    sqlite3_finalize(statement);
 }
 
 static jint nativeGetParameterCount(JNIEnv* env, jclass clazz, jint connectionPtr,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 806406d..8e5b509 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -646,9 +646,29 @@
     <bool translatable="false" name="skip_restoring_network_selection">false</bool>
 
     <!-- Maximum number of database connections opened and managed by framework layer
-         to handle queries on each database. -->
+         to handle queries on each database when using Write-Ahead Logging. -->
     <integer name="db_connection_pool_size">4</integer>
 
+    <!-- The default journal mode to use use when Write-Ahead Logging is not active.
+         Choices are: OFF, DELETE, TRUNCATE, PERSIST and MEMORY.
+         PERSIST may improve performance by reducing how often journal blocks are
+         reallocated (compared to truncation) resulting in better data block locality
+         and less churn of the storage media. -->
+    <string name="db_default_journal_mode">TRUNCATE</string>
+
+    <!-- Maximum size of the persistent journal file in bytes.
+         If the journal file grows to be larger than this amount then SQLite will
+         truncate it after committing the transaction. -->
+    <integer name="db_journal_size_limit">524288</integer>
+
+    <!-- The database synchronization mode.
+         Choices are: FULL, NORMAL, OFF. -->
+    <string name="db_sync_mode">FULL</string>
+
+    <!-- The Write-Ahead Log auto-checkpoint interval in database pages.
+         The log is checkpointed automatically whenever it exceeds this many pages. -->
+    <integer name="db_wal_autocheckpoint">1</integer>
+
     <!-- Max space (in MB) allocated to DownloadManager to store the downloaded
          files if they are to be stored in DownloadManager's data dir,
          which typically is /data/data/com.android.providers.downloads/files -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 94a671d..15499fe 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -250,6 +250,8 @@
   <java-symbol type="integer" name="config_wifi_framework_scan_interval" />
   <java-symbol type="integer" name="config_wifi_supplicant_scan_interval" />
   <java-symbol type="integer" name="db_connection_pool_size" />
+  <java-symbol type="integer" name="db_journal_size_limit" />
+  <java-symbol type="integer" name="db_wal_autocheckpoint" />
   <java-symbol type="integer" name="max_action_buttons" />
 
   <java-symbol type="color" name="tab_indicator_text_v4" />
@@ -438,6 +440,8 @@
   <java-symbol type="string" name="day_of_week_shortest_thursday" />
   <java-symbol type="string" name="day_of_week_shortest_tuesday" />
   <java-symbol type="string" name="day_of_week_shortest_wednesday" />
+  <java-symbol type="string" name="db_default_journal_mode" />
+  <java-symbol type="string" name="db_sync_mode" />
   <java-symbol type="string" name="decline" />
   <java-symbol type="string" name="default_permission_group" />
   <java-symbol type="string" name="default_text_encoding" />
diff --git a/docs/html/design/building-blocks/buttons.html b/docs/html/design/building-blocks/buttons.html
index cc43fcb..9f9652f9 100644
--- a/docs/html/design/building-blocks/buttons.html
+++ b/docs/html/design/building-blocks/buttons.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -110,7 +114,8 @@
   <img src="../static/content/buttons_basic.png">
 </div>
 
-<h2>Basic Buttons</h2>
+<h2 id="basic">Basic Buttons</h2>
+
 <p>Basic buttons are traditional buttons with borders and background. Android supports two styles for
 basic buttons: default and small. Default buttons have slightly larger font size and are optimized
 for display outside of form content. Small buttons are intended for display alongside other content.
@@ -131,7 +136,8 @@
   </div>
 </div>
 
-<h2>Borderless Buttons</h2>
+<h2 id="borderless">Borderless Buttons</h2>
+
 <p>Borderless buttons resemble basic buttons except that they have no borders or background. You can
 use borderless buttons with both icons and text. Borderless buttons are visually more lightweight
 than basic buttons and integrate nicely with other content.</p>
diff --git a/docs/html/design/building-blocks/dialogs.html b/docs/html/design/building-blocks/dialogs.html
index fc00780..f03a57a 100644
--- a/docs/html/design/building-blocks/dialogs.html
+++ b/docs/html/design/building-blocks/dialogs.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/building-blocks/grid-lists.html b/docs/html/design/building-blocks/grid-lists.html
index b4cfdcb..3f60216 100644
--- a/docs/html/design/building-blocks/grid-lists.html
+++ b/docs/html/design/building-blocks/grid-lists.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -159,7 +163,7 @@
 </div>
 
 
-<h2 id="with_labels">Grid List with Labels</h2>
+<h2 id="with-labels">Grid List with Labels</h2>
 
 <p>Use labels to display additional contextual information for your grid list items.</p>
 
diff --git a/docs/html/design/building-blocks/index.html b/docs/html/design/building-blocks/index.html
index c99d85c..029cabf 100644
--- a/docs/html/design/building-blocks/index.html
+++ b/docs/html/design/building-blocks/index.html
@@ -93,6 +93,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/building-blocks/lists.html b/docs/html/design/building-blocks/lists.html
index 914ae9e..dfd13d9 100644
--- a/docs/html/design/building-blocks/lists.html
+++ b/docs/html/design/building-blocks/lists.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/building-blocks/pickers.html b/docs/html/design/building-blocks/pickers.html
index 4c95a9f..fc9989c 100644
--- a/docs/html/design/building-blocks/pickers.html
+++ b/docs/html/design/building-blocks/pickers.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -123,7 +127,7 @@
   </div>
 </div>
 
-<h2>Date and time pickers</h2>
+<h2 id="date-time">Date and time pickers</h2>
 
 <p>Android provides these as ready-to-use dialogs. Each picker is a dialog with a set of controls for
 entering the parts of the date (month, day, year) or time (hour, minute, AM/PM). Using these in your
diff --git a/docs/html/design/building-blocks/progress.html b/docs/html/design/building-blocks/progress.html
index 7aae913..32183bc 100644
--- a/docs/html/design/building-blocks/progress.html
+++ b/docs/html/design/building-blocks/progress.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -105,7 +109,8 @@
 
 <p>When an operation of interest to the user is taking place over a relatively long period of time,
 provide visual feedback that it's still happening and in the process of being completed.</p>
-<h2>Progress</h2>
+<h2 id="progress">Progress</h2>
+
 <p>If you know the percentage of the operation that has been completed, use a determinate progress bar
 to give the user a sense of how much longer it will take.</p>
 
diff --git a/docs/html/design/building-blocks/scrolling.html b/docs/html/design/building-blocks/scrolling.html
index 3f1167c..3599a97 100644
--- a/docs/html/design/building-blocks/scrolling.html
+++ b/docs/html/design/building-blocks/scrolling.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -104,7 +108,8 @@
 
 <p>Scrolling allows the user to navigate to content in the overflow using a swipe gesture. The
 scrolling speed is proportional to the speed of the gesture.</p>
-<h2>Scroll Indicator</h2>
+<h2 id="indicator">Scroll Indicator</h2>
+
 <p>Appears during scrolling to indicate what portion of the content is currently in view.</p>
 
 <div class="framed-galaxynexus-land-span-13">
@@ -118,7 +123,8 @@
   <div class="video-instructions">&nbsp;</div>
 </div>
 
-<h2>Index Scrolling</h2>
+<h2 id="index-scrolling">Index Scrolling</h2>
+
 <p>In addition to traditional scrolling, a long alphabetical list can also offer index scrolling: a way
 to quickly navigate to the items that begin with a particular letter. With index scrolling, a scroll
 indicator appears even when the user isn't scrolling. Touching or dragging it causes the current
diff --git a/docs/html/design/building-blocks/seek-bars.html b/docs/html/design/building-blocks/seek-bars.html
index 84cf5d2..aef1823 100644
--- a/docs/html/design/building-blocks/seek-bars.html
+++ b/docs/html/design/building-blocks/seek-bars.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/building-blocks/spinners.html b/docs/html/design/building-blocks/spinners.html
index bf21fe8..5ef9d04 100644
--- a/docs/html/design/building-blocks/spinners.html
+++ b/docs/html/design/building-blocks/spinners.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/building-blocks/switches.html b/docs/html/design/building-blocks/switches.html
index 3d7bc9c..09af540 100644
--- a/docs/html/design/building-blocks/switches.html
+++ b/docs/html/design/building-blocks/switches.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -104,7 +108,8 @@
 
 <p>Switches allow the user to select options. There are three kinds of switches: checkboxes, radio
 buttons, and on/off switches.</p>
-<h2>Checkboxes</h2>
+<h2 id="checkboxes">Checkboxes</h2>
+
 <p>Checkboxes allow the user to select multiple options from a set. Avoid using a single checkbox to
 turn an option off or on. Instead, use an on/off switch.</p>
 
@@ -112,7 +117,8 @@
   <img src="../static/content/switches_checkboxes.png">
 </div>
 
-<h2>Radio Buttons</h2>
+<h2 id="radio-buttons">Radio Buttons</h2>
+
 <p>Radio buttons allow the user to select one option from a set. Use radio buttons for exclusive
 selection if you think that the user needs to see all available options side-by-side. Otherwise,
 consider a spinner, which uses less space.</p>
@@ -121,7 +127,8 @@
   <img src="../static/content/switches_radios.png">
 </div>
 
-<h2>On/off Switches</h2>
+<h2 id="switches">On/off Switches</h2>
+
 <p>On/off switches toggle the state of a single settings option.</p>
 
 <div style="text-align: center">
diff --git a/docs/html/design/building-blocks/tabs.html b/docs/html/design/building-blocks/tabs.html
index c094cce..d4b0e52 100644
--- a/docs/html/design/building-blocks/tabs.html
+++ b/docs/html/design/building-blocks/tabs.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/building-blocks/text-fields.html b/docs/html/design/building-blocks/text-fields.html
index 6496fa5..b9ec42d 100644
--- a/docs/html/design/building-blocks/text-fields.html
+++ b/docs/html/design/building-blocks/text-fields.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -141,7 +145,7 @@
   </div>
 </div>
 
-<h2>Text Selection</h2>
+<h2 id="text-selection">Text Selection</h2>
 
 <p>Users can select any word in a text field with a long press. This action triggers a text selection
 mode that facilitates extending the selection or choosing an action to perform on the selected text.
diff --git a/docs/html/design/downloads/index.html b/docs/html/design/downloads/index.html
new file mode 100644
index 0000000..f910b29
--- /dev/null
+++ b/docs/html/design/downloads/index.html
@@ -0,0 +1,278 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>
+
+Android Design - Downloads
+    </title>
+    <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
+    <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Roboto:regular,medium,thin,italic,mediumitalic">
+    <link rel="stylesheet" href="../static/yui-3.3.0-reset-min.css">
+    <link rel="stylesheet" href="../static/default.css">
+
+  </head>
+  <body>
+
+    <div id="page-container">
+
+      <div id="page-header"><a href="../index.html">Android Design</a></div>
+
+      <div id="main-row">
+
+        <ul id="nav">
+
+          <li class="nav-section">
+            <div class="nav-section-header"><a href="../index.html">Get Started</a></div>
+            <ul>
+              <li><a href="../get-started/creative-vision.html">Creative Vision</a></li>
+              <li><a href="../get-started/principles.html">Design Principles</a></li>
+              <li><a href="../get-started/ui-overview.html">UI Overview</a></li>
+            </ul>
+          </li>
+
+          <li class="nav-section">
+            <div class="nav-section-header"><a href="../style/index.html">Style</a></div>
+            <ul>
+              <li><a href="../style/devices-displays.html">Devices and Displays</a></li>
+              <li><a href="../style/themes.html">Themes</a></li>
+              <li><a href="../style/touch-feedback.html">Touch Feedback</a></li>
+              <li><a href="../style/metrics-grids.html">Metrics and Grids</a></li>
+              <li><a href="../style/typography.html">Typography</a></li>
+              <li><a href="../style/color.html">Color</a></li>
+              <li><a href="../style/iconography.html">Iconography</a></li>
+              <li><a href="../style/writing.html">Writing Style</a></li>
+            </ul>
+          </li>
+
+          <li class="nav-section">
+            <div class="nav-section-header"><a href="../patterns/index.html">Patterns</a></div>
+            <ul>
+              <li><a href="../patterns/new-4-0.html">New in Android 4.0</a></li>
+              <li><a href="../patterns/gestures.html">Gestures</a></li>
+              <li><a href="../patterns/app-structure.html">App Structure</a></li>
+              <li><a href="../patterns/navigation.html">Navigation</a></li>
+              <li><a href="../patterns/actionbar.html">Action Bar</a></li>
+              <li><a href="../patterns/multi-pane-layouts.html">Multi-pane Layouts</a></li>
+              <li><a href="../patterns/swipe-views.html">Swipe Views</a></li>
+              <li><a href="../patterns/selection.html">Selection</a></li>
+              <li><a href="../patterns/notifications.html">Notifications</a></li>
+              <li><a href="../patterns/compatibility.html">Compatibility</a></li>
+              <li><a href="../patterns/pure-android.html">Pure Android</a></li>
+            </ul>
+          </li>
+
+          <li class="nav-section">
+            <div class="nav-section-header"><a href="../building-blocks/index.html">Building Blocks</a></div>
+            <ul>
+              <li><a href="../building-blocks/tabs.html">Tabs</a></li>
+              <li><a href="../building-blocks/lists.html">Lists</a></li>
+              <li><a href="../building-blocks/grid-lists.html">Grid Lists</a></li>
+              <li><a href="../building-blocks/scrolling.html">Scrolling</a></li>
+              <li><a href="../building-blocks/spinners.html">Spinners</a></li>
+              <li><a href="../building-blocks/buttons.html">Buttons</a></li>
+              <li><a href="../building-blocks/text-fields.html">Text Fields</a></li>
+              <li><a href="../building-blocks/seek-bars.html">Seek Bars</a></li>
+              <li><a href="../building-blocks/progress.html">Progress &amp; Activity</a></li>
+              <li><a href="../building-blocks/switches.html">Switches</a></li>
+              <li><a href="../building-blocks/dialogs.html">Dialogs</a></li>
+              <li><a href="../building-blocks/pickers.html">Pickers</a></li>
+            </ul>
+          </li>
+
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
+          <li>
+            <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
+          </li>
+
+        </ul>
+
+        <div id="content">
+
+          
+          <div class="layout-content-row content-header">
+            <div class="layout-content-col span-9">
+              <h2>Downloads</h2>
+            </div>
+            <div class="paging-links layout-content-col span-4">
+              <a href="#" class="prev-page-link">Previous</a>
+              <a href="#" class="next-page-link">Next</a>
+            </div>
+          </div>
+          
+
+          
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-9">
+
+<p>Want everything? We've bundled all the downloads available on Android Design into a single ZIP file.
+You can also download individual files listed below.</p>
+<p>You may use these materials without restriction in your apps and to develop your apps.</p>
+
+  </div>
+  <div class="layout-content-col span-4">
+
+<p>
+  <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Downloads_20120229.zip">Download All</a>
+</p>
+
+  </div>
+</div>
+
+<h2 id="stencils">Stencils and Sources</h2>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-5">
+
+<p>Drag and drop your way to beautifully designed Ice Cream Sandwich apps. The stencils feature the
+rich typography, colors, interactive controls, and icons found throughout Android 4.0, along with
+phone and tablet outlines to frame your creations. Source files for icons and controls are also
+available.</p>
+
+  </div>
+  <div class="layout-content-col span-4">
+
+    <img src="../static/content/downloads_stencils.png">
+
+  </div>
+  <div class="layout-content-col span-4">
+
+<p>
+  <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Fireworks_Stencil_20120229.png">Adobe&reg; Fireworks&reg; PNG Stencil</a>
+  <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_OmniGraffle_Stencil_20120229.graffle">Omni&reg; OmniGraffle&reg; Stencil</a>
+  <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Holo_Widgets_20120229.zip">Adobe&reg; Photoshop&reg; Sources</a>
+</p>
+
+  </div>
+</div>
+
+<h2 id="action-bar-icon-pack">Action Bar Icon Pack</h2>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-5">
+
+<p>Action bar icons are graphic buttons that represent the most important actions people can take
+within your app. <a href="../style/iconography.html">More on Action Bar Iconography</a></p>
+<p>The download package includes icons that are scaled for various screen densities and suitable for
+use with the Holo Light and Holo Dark themes. The package also includes unstyled icons that you can
+modify to match your theme, plus source files.</p>
+
+  </div>
+  <div class="layout-content-col span-4">
+
+    <img src="../static/content/iconography_actionbar_style.png">
+
+  </div>
+  <div class="layout-content-col span-4">
+
+<p>
+  <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Icons_20120229.zip">Action Bar Icon Pack</a>
+</p>
+
+  </div>
+</div>
+
+<h2 id="style">Style</h2>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-5">
+
+<h4>Roboto</h4>
+<p>Ice Cream Sandwich introduced a new type family named Roboto, created specifically for the
+requirements of UI and high-resolution screens.</p>
+<p><a href="../style/typography.html#actionbar">More on Roboto</a></p>
+
+  </div>
+  <div class="layout-content-col span-4">
+
+    <img src="../static/content/downloads_roboto_specimen_preview.png">
+
+  </div>
+  <div class="layout-content-col span-4">
+
+<p>
+  <a class="download-button" href="https://dl-ssl.google.com/android/design/Roboto_Hinted_20111129.zip">Roboto</a>
+  <a class="download-button" href="https://dl-ssl.google.com/android/design/Roboto_Specimen_Book_20111129.pdf">Specimen Book</a>
+</p>
+
+  </div>
+</div>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-5">
+
+<h4>Color</h4>
+<p>Blue is the standard accent color in Android's color palette. Each color has a corresponding darker
+shade that can be used as a complement when needed.</p>
+<p><a href="../style/color.html">More on Color</a></p>
+
+  </div>
+  <div class="layout-content-col span-4">
+
+    <img src="../static/content/downloads_color_swatches.png">
+
+  </div>
+  <div class="layout-content-col span-4">
+
+<p>
+  <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Color_Swatches_20120229.zip">Color Swatches</a>
+</p>
+
+  </div>
+</div>
+
+
+
+          
+          <div class="layout-content-row content-footer">
+            <div class="paging-links layout-content-col span-9">&nbsp;</div>
+            <div class="paging-links layout-content-col span-4">
+              <a href="#" class="prev-page-link">Previous</a>
+              <a href="#" class="next-page-link">Next</a>
+            </div>
+          </div>
+          
+        </div>
+
+      </div>
+
+      <div id="page-footer">
+
+        <p id="copyright">
+          Except as noted, this content is licensed under
+          <a href="http://creativecommons.org/licenses/by/2.5/">
+          Creative Commons Attribution 2.5</a>.<br>
+          For details and restrictions, see the
+          <a href="http://developer.android.com/license.html">Content License</a>.
+        </p>
+
+        <p>
+          <a href="http://www.android.com/terms.html">Site Terms of Service</a> &ndash;
+          <a href="http://www.android.com/privacy.html">Privacy Policy</a> &ndash;
+          <a href="http://www.android.com/branding.html">Brand Guidelines</a>
+        </p>
+
+      </div>
+    </div>
+
+    <script src="../static/jquery-1.6.2.min.js"></script>
+    <script>
+    var SITE_ROOT = '../';
+    </script>
+    <script src="../static/default.js"></script>
+
+
+    <script type="text/javascript">
+    var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+    document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+    </script>
+    <script type="text/javascript">
+    var pageTracker = _gat._getTracker("UA-5831155-1");
+    pageTracker._trackPageview();
+    </script>
+  </body>
+</html>
diff --git a/docs/html/design/get-started/creative-vision.html b/docs/html/design/get-started/creative-vision.html
index 11783c4..154f8d0 100644
--- a/docs/html/design/get-started/creative-vision.html
+++ b/docs/html/design/get-started/creative-vision.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/get-started/principles.html b/docs/html/design/get-started/principles.html
index 0d9ef20..f10a90d 100644
--- a/docs/html/design/get-started/principles.html
+++ b/docs/html/design/get-started/principles.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -106,7 +110,7 @@
 best interests in mind. Consider them as you apply your own creativity and design thinking. Deviate
 with purpose.</p>
 
-<h2>Enchant Me</h2>
+<h2 id="enchant-me">Enchant Me</h2>
 
 <div class="layout-content-row">
   <div class="layout-content-col span-7">
@@ -176,7 +180,7 @@
   </div>
 </div>
 
-<h2>Simplify My Life</h2>
+<h2 id="simplify-my-life">Simplify My Life</h2>
 
 <div class="layout-content-row">
   <div class="layout-content-col span-7">
@@ -312,7 +316,7 @@
   </div>
 </div>
 
-<h2>Make Me Amazing</h2>
+<h2 id="make-me-amazing">Make Me Amazing</h2>
 
 <div class="layout-content-row">
   <div class="layout-content-col span-7">
diff --git a/docs/html/design/get-started/ui-overview.html b/docs/html/design/get-started/ui-overview.html
index bd5ff9c..a4881d5 100644
--- a/docs/html/design/get-started/ui-overview.html
+++ b/docs/html/design/get-started/ui-overview.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -109,7 +113,7 @@
 in your app.</p>
 <p>Read on for a quick overview of the most important aspects of the Android user interface.</p>
 
-<h2>Home, All Apps, and Recents</h2>
+<h2 id="home-all-apps-recents">Home, All Apps, and Recents</h2>
 
 <div class="vspace size-1">&nbsp;</div>
 
@@ -153,7 +157,7 @@
   </div>
 </div>
 
-<h2>System Bars</h2>
+<h2 id="system-bars">System Bars</h2>
 
 <p>The system bars are screen areas dedicated to the display of notifications, communication of device
 status, and device navigation. Typically the system bars are displayed concurrently with your app.
@@ -185,7 +189,7 @@
 
 </div>
 
-<h2>Notifications</h2>
+<h2 id="notifications">Notifications</h2>
 
 <p>Notifications are brief messages that users can access at any time from the status bar. They
 provide updates, reminders, or information that's important, but not critical enough to warrant
diff --git a/docs/html/design/index.html b/docs/html/design/index.html
index 14d7b63..8583aa4 100644
--- a/docs/html/design/index.html
+++ b/docs/html/design/index.html
@@ -93,6 +93,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/patterns/actionbar.html b/docs/html/design/patterns/actionbar.html
index 911c549..1566d04 100644
--- a/docs/html/design/patterns/actionbar.html
+++ b/docs/html/design/patterns/actionbar.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -118,7 +122,8 @@
 <p>If you're new to writing Android apps, note that the action bar is one of the most important design
 elements you can implement. Following the guidelines described here will go a long way toward making
 your app's interface consistent with the core Android apps.</p>
-<h2>General Organization</h2>
+<h2 id="organization">General Organization</h2>
+
 <p>The action bar is split into four different functional areas that apply to most apps.</p>
 <img src="../static/content/action_bar_basics.png">
 
@@ -129,8 +134,8 @@
       <li class="value-1"><h4>App icon</h4>
         <p>
 
-The app icon establishes your app's identity. It can be replaced with a different logo or branding if
-you wish.
+The app icon establishes your app's identity. It can be replaced with a different logo or branding
+if you wish.
 Important: If the app is currently not displaying the top-level screen, be sure to display the Up
 caret to the left of the app icon, so the user can navigate up the hierarchy. For more discussion of
 Up navigation, see the <a href="../patterns/navigation.html">Navigation</a> pattern.
@@ -180,12 +185,11 @@
         </p>
       </li>
     </ol>
-
   </div>
 </div>
 
+<h2 id="adapting-rotation">Adapting to Rotation and Different Screen Sizes</h2>
 
-<h2>Adapting to Rotation and Different Screen Sizes</h2>
 <p>One of the most important UI issues to consider when creating an app is how to adjust to screen
 rotation on different screen sizes.</p>
 <p>You can adapt to such changes by using <em>split action bars</em>, which allow you to distribute action bar
@@ -196,7 +200,7 @@
   Split action bar showing action buttons at the bottom of the screen in vertical orientation.
 </div>
 
-<h2>Layout Considerations for Split Action Bars</h2>
+<h2 id="considerations-split-action-bars">Layout Considerations for Split Action Bars</h2>
 
 <div class="layout-content-row">
   <div class="layout-content-col span-8 with-callouts">
@@ -222,7 +226,8 @@
   </div>
 </div>
 
-<h2>Contextual Action Bars</h2>
+<h2 id="contextual">Contextual Action Bars</h2>
+
 <p>A <em>contextual action bar (CAB)</em> is a temporary action bar that overlays the app's action bar for the
 duration of a particular sub-task. CABs are most typically used for tasks that involve acting on
 selected data or text.</p>
@@ -244,7 +249,8 @@
 <p>Use CABs whenever you allow the user to select data via long press. You can control the action
 content of a CAB in order to insert the actions you would like the user to be able to perform.</p>
 <p>For more information, refer to the "Selection" pattern.</p>
-<h2>Action Bar Elements</h2>
+<h2 id="elements">Action Bar Elements</h2>
+
 <h4>Tabs</h4>
 <p><em>Tabs</em> display app views concurrently and make it easy to explore and switch between them. Use tabs
 if you expect your users to switch views frequently.</p>
@@ -326,7 +332,9 @@
 <p><em>Action buttons</em> on the action bar surface your app's most important activities. Think about which
 buttons will get used most often, and order them accordingly. Depending on available screen real
 estate, the system shows your most important actions as action buttons and moves the rest to the
-action overflow.</p>
+action overflow. The action bar and the action overflow should only present actions to the user that
+are available. If an action is unavailable in the current context, hide it. Do not show it as
+disabled.</p>
 
 <img src="../static/content/action_bar_pattern_action_icons.png">
 <div class="figure-caption">
@@ -380,8 +388,7 @@
 </p>
 <p>
 
-<a href="../static/download/action_bar_icons-v4.0.zip">Download the Action Bar Icon
-Pack</a>
+<a href="https://dl-ssl.google.com/android/design/Android_Design_Icons_20120229.zip">Download the Action Bar Icon Pack</a>
 
 </p>
 
@@ -436,7 +443,8 @@
   The Gallery app's share action provider with extended spinner for additional sharing options.
 </div>
 
-<h2>Action Bar Checklist</h2>
+<h2 id="checklist">Action Bar Checklist</h2>
+
 <p>When planning your split action bars, ask yourself questions like these:</p>
 <h4>How important is view navigation to the task?</h4>
 <p>If view navigation is very important to your app, use tabs (for fastest view-switching) or spinners.</p>
diff --git a/docs/html/design/patterns/app-structure.html b/docs/html/design/patterns/app-structure.html
index fb9205b..1b48280 100644
--- a/docs/html/design/patterns/app-structure.html
+++ b/docs/html/design/patterns/app-structure.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -111,7 +115,8 @@
 <li>Apps such as Gmail or Market that combine a broad set of data views with deep navigation</li>
 </ul>
 <p>Your app's structure depends largely on the content and tasks you want to surface for your users.</p>
-<h2>General Structure</h2>
+<h2 id="general-structure">General Structure</h2>
+
 <p>A typical Android app consists of top level and detail/edit views. If the navigation hierarchy is
 deep and complex, category views connect top level and detail views.</p>
 
@@ -139,7 +144,8 @@
   </div>
 </div>
 
-<h2>Top Level</h2>
+<h2 id="top-level">Top Level</h2>
+
 <p>The layout of your start screen requires special attention. This is the first screen people see
 after launching your app, so it should be an equally rewarding experience for new and frequent
 visitors alike.</p>
@@ -219,7 +225,8 @@
   </div>
 </div>
 
-<h2>Categories</h2>
+<h2 id="categories">Categories</h2>
+
 <p>Generally, the purpose of a deep, data-driven app is to navigate through organizational categories
 to the detail level, where data can be viewed and managed. Minimize perceived navigation effort by
 keeping your apps shallow.</p>
@@ -281,7 +288,8 @@
 delete multiple items in the category view. Analyze which detail view actions are applicable to
 collections of items. Then use multi-select to allow application of those actions to multiple items
 in a category view.</p>
-<h2>Details</h2>
+<h2 id="details">Details</h2>
+
 <p>The detail view allows you to view and act on your data. The layout of the detail view depends on
 the data type being displayed, and therefore differs widely among apps.</p>
 
@@ -330,7 +338,8 @@
   filmstrip control that lets people quickly jump to specific images.
 </div>
 
-<h2>Checklist</h2>
+<h2 id="checklist">Checklist</h2>
+
 <ul>
 <li>
 <p>Find ways to display useful content on your start screen.</p>
diff --git a/docs/html/design/patterns/compatibility.html b/docs/html/design/patterns/compatibility.html
index f18c62d..d6e59f5 100644
--- a/docs/html/design/patterns/compatibility.html
+++ b/docs/html/design/patterns/compatibility.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -110,7 +114,7 @@
 </ul>
 <p>Android 4.0 brings these changes for tablets to the phone platform.</p>
 
-<h2>Adapting Android 4.0 to Older Hardware and Apps</h2>
+<h2 id="older-hardware">Adapting Android 4.0 to Older Hardware and Apps</h2>
 
 <div class="layout-content-row">
   <div class="layout-content-col span-6">
diff --git a/docs/html/design/patterns/gestures.html b/docs/html/design/patterns/gestures.html
index f8585e4..c88817f 100644
--- a/docs/html/design/patterns/gestures.html
+++ b/docs/html/design/patterns/gestures.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/patterns/index.html b/docs/html/design/patterns/index.html
index ff797db..863baa9 100644
--- a/docs/html/design/patterns/index.html
+++ b/docs/html/design/patterns/index.html
@@ -93,6 +93,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/patterns/multi-pane-layouts.html b/docs/html/design/patterns/multi-pane-layouts.html
index af05e31..7925c98 100644
--- a/docs/html/design/patterns/multi-pane-layouts.html
+++ b/docs/html/design/patterns/multi-pane-layouts.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -108,7 +112,8 @@
 <p><em>Panels</em> are a great way for your app to achieve this. They allow you to combine multiple views into
 one compound view when a lot of horizontal screen real estate is available and by splitting them up
 when less space is available.</p>
-<h2>Combining Multiple Views Into One</h2>
+<h2 id="combining-views">Combining Multiple Views Into One</h2>
+
 <p>On smaller devices your content is typically divided into a master grid or list view and a detail
 view. Touching an item in the master view opens a different screen showing that item's detail
 information.</p>
@@ -124,7 +129,8 @@
 <p>In general, use the pane on the right to present more information about the item you selected in the
 left pane. Make sure to keep the item in the left pane selected in order to establish the
 relationship between the panels.</p>
-<h2>Compound Views and Orientation Changes</h2>
+<h2 id="orientation">Compound Views and Orientation Changes</h2>
+
 <p>Screens should have the same functionality regardless of orientation. If you use a compound view in
 one orientation, don't split it up when the user rotates the screen. There are several techniques
 you can use to adjust the layout after orientation change while keeping functional parity intact.</p>
@@ -189,7 +195,8 @@
   </div>
 </div>
 
-<h2>Checklist</h2>
+<h2 id="checklist">Checklist</h2>
+
 <ul>
 <li>
 <p>Plan in advance on how your app scales to different screen sizes and screen orientations.</p>
diff --git a/docs/html/design/patterns/navigation.html b/docs/html/design/patterns/navigation.html
index cad3682..6287b5e 100644
--- a/docs/html/design/patterns/navigation.html
+++ b/docs/html/design/patterns/navigation.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -112,7 +116,8 @@
 
 <img src="../static/content/navigation_with_back_and_up.png">
 
-<h2>Up vs. Back</h2>
+<h2 id="up-vs-back">Up vs. Back</h2>
+
 <p>The Up button is used to navigate within an application based on the hierarchical relationships
 between screens. For instance, if screen A displays a list of items, and selecting an item leads to
 screen B (which presents that item in more detail), then screen B should offer an Up button that
@@ -134,7 +139,8 @@
 <li>Back dismisses contextual action bars, and removes the highlight from the selected items</li>
 <li>Back hides the onscreen keyboard (IME)</li>
 </ul>
-<h2>Navigation Within Your App</h2>
+<h2 id="within-app">Navigation Within Your App</h2>
+
 <h4>Navigating to screens with multiple entry points</h4>
 <p>Sometimes a screen doesn't have a strict position within the app's hierarchy, and can be reached
 from multiple entry points&mdash;e.g., a settings screen which can be navigated to from any screen
@@ -176,7 +182,8 @@
 
 <img src="../static/content/navigation_between_siblings_market2.png">
 
-<h2>Navigation From Outside Your App</h2>
+<h2 id="from-outside">Navigation From Outside Your App</h2>
+
 <p>There are two categories of navigation from outside your app to screens deep within the app's
 hierarchy:</p>
 <ul>
diff --git a/docs/html/design/patterns/new-4-0.html b/docs/html/design/patterns/new-4-0.html
index 272b079..2e2cbc2 100644
--- a/docs/html/design/patterns/new-4-0.html
+++ b/docs/html/design/patterns/new-4-0.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/patterns/notifications.html b/docs/html/design/patterns/notifications.html
index c5045ae..99af418 100644
--- a/docs/html/design/patterns/notifications.html
+++ b/docs/html/design/patterns/notifications.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -173,7 +177,7 @@
   </div>
 </div>
 
-<h2>Design Guidelines</h2>
+<h2 id="design-guidelines">Design Guidelines</h2>
 
 <div class="layout-content-row">
   <div class="layout-content-col span-6">
@@ -269,7 +273,7 @@
 <li>Use color to distinguish your app from others. Notification icons should generally be monochrome.</li>
 </ul>
 
-<h2>Interacting With Notifications</h2>
+<h2 id="interacting-with-notifications">Interacting With Notifications</h2>
 
 <div class="layout-content-row">
   <div class="layout-content-col span-6">
diff --git a/docs/html/design/patterns/pure-android.html b/docs/html/design/patterns/pure-android.html
index 507558a..f5a8042 100644
--- a/docs/html/design/patterns/pure-android.html
+++ b/docs/html/design/patterns/pure-android.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -227,7 +231,7 @@
   </div>
 </div>
 
-<h2>Device Independence</h2>
+<h2 id="device-independence">Device Independence</h2>
 
 <p>Remember that your app will run on a wide variety of different screen sizes. Create visual assets
 for different screen sizes and densities and make use of concepts such as multi-pane layouts to
diff --git a/docs/html/design/patterns/selection.html b/docs/html/design/patterns/selection.html
index 37dcab5..44c6a84 100644
--- a/docs/html/design/patterns/selection.html
+++ b/docs/html/design/patterns/selection.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -180,7 +184,8 @@
   </div>
 </div>
 
-<h2>Checklist</h2>
+<h2 id="checklist">Checklist</h2>
+
 <ul>
 <li>
 <p>Whenever your app supports the selection of multiple data items, make use of the contextual action
diff --git a/docs/html/design/patterns/swipe-views.html b/docs/html/design/patterns/swipe-views.html
index 4e8cd03..acc9f39 100644
--- a/docs/html/design/patterns/swipe-views.html
+++ b/docs/html/design/patterns/swipe-views.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -107,7 +111,8 @@
 vertical hierarchies and make access to related data items faster and more enjoyable. Swipe views
 allow the user to efficiently move from item to item using a simple gesture and thereby make
 browsing and consuming data a more fluent experience.</p>
-<h2>Swiping Between Detail Views</h2>
+<h2 id="detail-views">Swiping Between Detail Views</h2>
+
 <p>An app's data is often organized in a master/detail relationship: The user can view a list of
 related data items, such as images, chats, or emails, and then pick one of the items to see the
 detail contents in a separate screen.</p>
@@ -127,7 +132,7 @@
   Navigating between consecutive Email messages using the swipe gesture.
 </div>
 
-<h2>Swiping Between Tabs</h2>
+<h2 id="between-tabs">Swiping Between Tabs</h2>
 
 <div class="layout-content-row">
   <div class="layout-content-col span-5">
@@ -150,7 +155,8 @@
 <p>If your app uses action bar tabs, use swipe to navigate between the different views.</p>
 <div class="vspace size-2">&nbsp;</div>
 
-<h2>Checklist</h2>
+<h2 id="checklist">Checklist</h2>
+
 <ul>
 <li>
 <p>Use swipe to quickly navigate between detail views or tabs.</p>
diff --git a/docs/html/design/static/content/downloads_color_swatches.png b/docs/html/design/static/content/downloads_color_swatches.png
new file mode 100644
index 0000000..af2b24f
--- /dev/null
+++ b/docs/html/design/static/content/downloads_color_swatches.png
Binary files differ
diff --git a/docs/html/design/static/content/downloads_roboto_specimen_preview.png b/docs/html/design/static/content/downloads_roboto_specimen_preview.png
new file mode 100644
index 0000000..2954cbf
--- /dev/null
+++ b/docs/html/design/static/content/downloads_roboto_specimen_preview.png
Binary files differ
diff --git a/docs/html/design/static/content/downloads_stencils.png b/docs/html/design/static/content/downloads_stencils.png
new file mode 100644
index 0000000..9e09319
--- /dev/null
+++ b/docs/html/design/static/content/downloads_stencils.png
Binary files differ
diff --git a/docs/html/design/static/default.css b/docs/html/design/static/default.css
index 42ab527..3aa1db3 100644
--- a/docs/html/design/static/default.css
+++ b/docs/html/design/static/default.css
@@ -3,8 +3,8 @@
 /* clearfix idiom */
 /* common mixins */
 /* page layout + top-level styles */
-::-moz-selection,
 ::-webkit-selection,
+::-moz-selection,
 ::selection {
   background-color: #0099cc;
   color: #fff; }
@@ -256,6 +256,8 @@
       position: absolute;
       top: 10px;
       right: 10px; }
+    #nav .nav-section-header.empty:after {
+      display: none; }
   #nav li.expanded .nav-section-header {
     background: rgba(0, 0, 0, 0.05); }
     #nav li.expanded .nav-section-header:after {
@@ -268,9 +270,9 @@
     overflow: hidden;
     margin-bottom: 0; }
     #nav > li > ul.animate-height {
-      transition: height 0.25s ease-in;
       -webkit-transition: height 0.25s ease-in;
-      -moz-transition: height 0.25s ease-in; }
+      -moz-transition: height 0.25s ease-in;
+      transition: height 0.25s ease-in; }
     #nav > li > ul li {
       padding: 10px 10px 11px 10px; }
   #nav > li.expanded > ul {
@@ -330,6 +332,39 @@
       margin-left: 5px; }
 
 /* content body */
+@-webkit-keyframes glowheader {
+  from {
+    background-color: #33b5e5;
+    color: #000;
+    border-bottom-color: #000; }
+
+  to {
+    background-color: transparent;
+    color: #33b5e5;
+    border-bottom-color: #33b5e5; } }
+
+@-moz-keyframes glowheader {
+  from {
+    background-color: #33b5e5;
+    color: #000;
+    border-bottom-color: #000; }
+
+  to {
+    background-color: transparent;
+    color: #33b5e5;
+    border-bottom-color: #33b5e5; } }
+
+@keyframes glowheader {
+  from {
+    background-color: #33b5e5;
+    color: #000;
+    border-bottom-color: #000; }
+
+  to {
+    background-color: transparent;
+    color: #33b5e5;
+    border-bottom-color: #33b5e5; } }
+
 #content p,
 #content ul,
 #content ol,
@@ -345,6 +380,16 @@
   color: #33b5e5;
   border-bottom: 1px solid #33b5e5;
   height: 30px; }
+  #content h2:target {
+    -webkit-animation-name: glowheader;
+    -moz-animation-name: glowheader;
+    animation-name: glowheader;
+    -webkit-animation-duration: 0.7s;
+    -moz-animation-duration: 0.7s;
+    animation-duration: 0.7s;
+    -webkit-animation-timing-function: ease-out;
+    -moz-animation-timing-function: ease-out;
+    animation-timing-function: ease-out; }
 #content hr {
   border: 0;
   border-bottom: 1px solid #33b5e5;
@@ -569,3 +614,20 @@
     margin-right: 8px; }
   .video-instructions:after {
     content: 'Click to replay movie.'; }
+
+/* download buttons */
+.download-button {
+  display: block;
+  margin-bottom: 5px;
+  text-decoration: none;
+  background-color: #33b5e5;
+  color: #fff !important;
+  font-weight: 500;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.12);
+  padding: 6px 12px;
+  border-radius: 2px; }
+  .download-button:hover, .download-button:focus {
+    background-color: #0099cc;
+    color: #fff !important; }
+  .download-button:active {
+    background-color: #006699; }
diff --git a/docs/html/design/static/default.js b/docs/html/design/static/default.js
index 6721ab8..b213dd9 100644
--- a/docs/html/design/static/default.js
+++ b/docs/html/design/static/default.js
@@ -158,4 +158,12 @@
       $tooltip.hide();
     });
   });
+
+  // Set up <h2> deeplinks
+  $('h2').click(function() {
+    var id = $(this).attr('id');
+    if (id) {
+      document.location.hash = id;
+    }
+  });
 });
\ No newline at end of file
diff --git a/docs/html/design/static/download/RobotoSpecimenBook.pdf b/docs/html/design/static/download/RobotoSpecimenBook.pdf
deleted file mode 100644
index 594a366..0000000
--- a/docs/html/design/static/download/RobotoSpecimenBook.pdf
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/static/download/Roboto_Hinted_20111129.zip b/docs/html/design/static/download/Roboto_Hinted_20111129.zip
deleted file mode 100644
index 3d3ab77..0000000
--- a/docs/html/design/static/download/Roboto_Hinted_20111129.zip
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/static/download/action_bar_icons-v4.0.zip b/docs/html/design/static/download/action_bar_icons-v4.0.zip
deleted file mode 100644
index 4568894..0000000
--- a/docs/html/design/static/download/action_bar_icons-v4.0.zip
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/static/download/color_swatches.zip b/docs/html/design/static/download/color_swatches.zip
deleted file mode 100644
index 0221d7b..0000000
--- a/docs/html/design/static/download/color_swatches.zip
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/static/ico_styleguide.png b/docs/html/design/static/ico_styleguide.png
deleted file mode 100644
index c12907c..0000000
--- a/docs/html/design/static/ico_styleguide.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/style/color.html b/docs/html/design/style/color.html
index 893e09e..bca3c45 100644
--- a/docs/html/design/style/color.html
+++ b/docs/html/design/style/color.html
@@ -173,6 +173,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -218,10 +222,11 @@
       </ul>
     </div>
 
-<h2>Palette</h2>
+<h2 id="palette">Palette</h2>
+
 <p>Blue is the standard accent color in Android's color palette. Each color has a corresponding darker
 shade that can be used as a complement when needed.</p>
-<p><a href="../static/download/color_swatches.zip">Download the swatches</a></p>
+<p><a href="https://dl-ssl.google.com/android/design/Android_Design_Color_Swatches_20120229.zip">Download the swatches</a></p>
 
 <img src="../static/content/color_spectrum.png">
 
diff --git a/docs/html/design/style/devices-displays.html b/docs/html/design/style/devices-displays.html
index 9fba719..89e0876 100644
--- a/docs/html/design/style/devices-displays.html
+++ b/docs/html/design/style/devices-displays.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/style/iconography.html b/docs/html/design/style/iconography.html
index 5d5d200..96954de 100644
--- a/docs/html/design/style/iconography.html
+++ b/docs/html/design/style/iconography.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -191,7 +195,7 @@
 </div>
 
 
-<h2 id="actionbar">Action Bar</h2>
+<h2 id="action-bar">Action Bar</h2>
 
 <p>
 
@@ -211,8 +215,7 @@
 </p>
 <p>
 
-<a href="../static/download/action_bar_icons-v4.0.zip">Download the Action Bar Icon
-Pack</a>
+<a href="https://dl-ssl.google.com/android/design/Android_Design_Icons_20120229.zip">Download the Action Bar Icon Pack</a>
 
 </p>
 
@@ -290,7 +293,7 @@
 </div>
 
 
-<h2 id="small_contextual">Small / Contextual Icons</h2>
+<h2 id="small-contextual">Small / Contextual Icons</h2>
 
 <p>Within the body of your app, use small icons to surface actions and/or provide status for specific
 items. For example, in the Gmail app, each message has a star icon that marks the message as
diff --git a/docs/html/design/style/index.html b/docs/html/design/style/index.html
index 5ecbafa..c7ac58f 100644
--- a/docs/html/design/style/index.html
+++ b/docs/html/design/style/index.html
@@ -93,6 +93,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/style/metrics-grids.html b/docs/html/design/style/metrics-grids.html
index 17d4937..7bb9dd0 100644
--- a/docs/html/design/style/metrics-grids.html
+++ b/docs/html/design/style/metrics-grids.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -130,7 +134,7 @@
   </div>
 </div>
 
-<h2>48dp Rhythm</h2>
+<h2 id="48dp-rhythm">48dp Rhythm</h2>
 
 <p>Touchable UI components are generally laid out along 48dp units.</p>
 
@@ -157,7 +161,7 @@
 <h4>Mind the gaps</h4>
 <p>Spacing between each UI element is 8dp.</p>
 
-<h2>Examples</h2>
+<h2 id="examples">Examples</h2>
 
 <img src="../static/content/metrics_forms.png">
 
diff --git a/docs/html/design/style/themes.html b/docs/html/design/style/themes.html
index ada974d..a629978 100644
--- a/docs/html/design/style/themes.html
+++ b/docs/html/design/style/themes.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/style/touch-feedback.html b/docs/html/design/style/touch-feedback.html
index 0d49832..d1c08f8 100644
--- a/docs/html/design/style/touch-feedback.html
+++ b/docs/html/design/style/touch-feedback.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
diff --git a/docs/html/design/style/typography.html b/docs/html/design/style/typography.html
index d3cc769..d9b6d4b 100644
--- a/docs/html/design/style/typography.html
+++ b/docs/html/design/style/typography.html
@@ -80,6 +80,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -119,8 +123,8 @@
 
     <img src="../static/content/typography_alphas.png">
 
-<p><a href="../static/download/Roboto_Hinted_20111129.zip">Download Roboto</a></p>
-<p><a href="../static/download/RobotoSpecimenBook.pdf">Specimen Book</a></p>
+<p><a href="https://dl-ssl.google.com/android/design/Roboto_Hinted_20111129.zip">Download Roboto</a></p>
+<p><a href="https://dl-ssl.google.com/android/design/Roboto_Specimen_Book_20111129.pdf">Specimen Book</a></p>
 
   </div>
 </div>
diff --git a/docs/html/design/style/writing.html b/docs/html/design/style/writing.html
index e5f1ea8..146ce88 100644
--- a/docs/html/design/style/writing.html
+++ b/docs/html/design/style/writing.html
@@ -132,6 +132,10 @@
             </ul>
           </li>
 
+          <li class="nav-section">
+            <div class="nav-section-header empty"><a href="../downloads/index.html">Downloads</a></div>
+          </li>
+
           <li>
             <div id="back-dac-section"><a href="../../index.html">Developers</a></div>
           </li>
@@ -186,7 +190,7 @@
 </li>
 </ol>
 
-<h2>Examples</h2>
+<h2 id="examples">Examples</h2>
 
 <ol><li class="value-1"><strong>Keep it brief.</strong> From the setup wizard:</ol>
 
diff --git a/drm/drmserver/DrmManagerService.cpp b/drm/drmserver/DrmManagerService.cpp
index caeb026..8ba0203 100644
--- a/drm/drmserver/DrmManagerService.cpp
+++ b/drm/drmserver/DrmManagerService.cpp
@@ -159,12 +159,18 @@
 status_t DrmManagerService::consumeRights(
             int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve) {
     ALOGV("Entering consumeRights");
+    if (!isProtectedCallAllowed()) {
+        return DRM_ERROR_NO_PERMISSION;
+    }
     return mDrmManager->consumeRights(uniqueId, decryptHandle, action, reserve);
 }
 
 status_t DrmManagerService::setPlaybackStatus(
             int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) {
     ALOGV("Entering setPlaybackStatus");
+    if (!isProtectedCallAllowed()) {
+        return DRM_ERROR_NO_PERMISSION;
+    }
     return mDrmManager->setPlaybackStatus(uniqueId, decryptHandle, playbackStatus, position);
 }
 
@@ -229,12 +235,18 @@
 
 status_t DrmManagerService::closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle) {
     ALOGV("Entering closeDecryptSession");
+    if (!isProtectedCallAllowed()) {
+        return DRM_ERROR_NO_PERMISSION;
+    }
     return mDrmManager->closeDecryptSession(uniqueId, decryptHandle);
 }
 
 status_t DrmManagerService::initializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle,
             int decryptUnitId, const DrmBuffer* headerInfo) {
     ALOGV("Entering initializeDecryptUnit");
+    if (!isProtectedCallAllowed()) {
+        return DRM_ERROR_NO_PERMISSION;
+    }
     return mDrmManager->initializeDecryptUnit(uniqueId,decryptHandle, decryptUnitId, headerInfo);
 }
 
@@ -242,18 +254,27 @@
             int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId,
             const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) {
     ALOGV("Entering decrypt");
+    if (!isProtectedCallAllowed()) {
+        return DRM_ERROR_NO_PERMISSION;
+    }
     return mDrmManager->decrypt(uniqueId, decryptHandle, decryptUnitId, encBuffer, decBuffer, IV);
 }
 
 status_t DrmManagerService::finalizeDecryptUnit(
             int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) {
     ALOGV("Entering finalizeDecryptUnit");
+    if (!isProtectedCallAllowed()) {
+        return DRM_ERROR_NO_PERMISSION;
+    }
     return mDrmManager->finalizeDecryptUnit(uniqueId, decryptHandle, decryptUnitId);
 }
 
 ssize_t DrmManagerService::pread(int uniqueId, DecryptHandle* decryptHandle,
             void* buffer, ssize_t numBytes, off64_t offset) {
     ALOGV("Entering pread");
+    if (!isProtectedCallAllowed()) {
+        return DRM_ERROR_NO_PERMISSION;
+    }
     return mDrmManager->pread(uniqueId, decryptHandle, buffer, numBytes, offset);
 }
 
diff --git a/include/drm/drm_framework_common.h b/include/drm/drm_framework_common.h
index 2632cbd..637409c 100644
--- a/include/drm/drm_framework_common.h
+++ b/include/drm/drm_framework_common.h
@@ -43,6 +43,7 @@
     DRM_ERROR_DECRYPT                       = ERROR_BASE - 5,
     DRM_ERROR_CANNOT_HANDLE                 = ERROR_BASE - 6,
     DRM_ERROR_TAMPER_DETECTED               = ERROR_BASE - 7,
+    DRM_ERROR_NO_PERMISSION                 = ERROR_BASE - 8,
 
     DRM_NO_ERROR                            = NO_ERROR
 };
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 3df105b..f2bb6ec 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -39,6 +39,8 @@
 #define MAX_TEXT_CACHE_WIDTH 2048
 #define TEXTURE_BORDER_SIZE 2
 
+#define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16)
+
 ///////////////////////////////////////////////////////////////////////////////
 // CacheTextureLine
 ///////////////////////////////////////////////////////////////////////////////
@@ -163,6 +165,44 @@
     }
 }
 
+void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
+        SkPathMeasure& measure, SkPoint* position, SkVector* tangent) {
+    const float halfWidth = glyph->mBitmapWidth * 0.5f;
+    const float height = glyph->mBitmapHeight;
+
+    float nPenX = glyph->mBitmapLeft;
+    vOffset += glyph->mBitmapTop + height;
+
+    const float u1 = glyph->mBitmapMinU;
+    const float u2 = glyph->mBitmapMaxU;
+    const float v1 = glyph->mBitmapMinV;
+    const float v2 = glyph->mBitmapMaxV;
+
+    SkPoint destination[4];
+    measure.getPosTan(x + hOffset + nPenX + halfWidth, position, tangent);
+
+    // Move along the tangent and offset by the normal
+    destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset,
+            -tangent->fY * halfWidth + tangent->fX * vOffset);
+    destination[1].set(tangent->fX * halfWidth - tangent->fY * vOffset,
+            tangent->fY * halfWidth + tangent->fX * vOffset);
+    destination[2].set(destination[1].fX + tangent->fY * height,
+            destination[1].fY - tangent->fX * height);
+    destination[3].set(destination[0].fX + tangent->fY * height,
+            destination[0].fY - tangent->fX * height);
+
+    mState->appendRotatedMeshQuad(
+            position->fX + destination[0].fX,
+            position->fY + destination[0].fY, u1, v2,
+            position->fX + destination[1].fX,
+            position->fY + destination[1].fY, u2, v2,
+            position->fX + destination[2].fX,
+            position->fY + destination[2].fY, u2, v1,
+            position->fX + destination[3].fX,
+            position->fY + destination[3].fY, u1, v1,
+            glyph->mCachedTextureLine->mCacheTexture);
+}
+
 CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) {
     CachedGlyphInfo* cachedGlyph = NULL;
     ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
@@ -198,6 +238,56 @@
             0, 0, NULL, positions);
 }
 
+void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+        int numGlyphs, SkPath* path, float hOffset, float vOffset) {
+    if (numGlyphs == 0 || text == NULL || len == 0) {
+        return;
+    }
+
+    text += start;
+
+    int glyphsCount = 0;
+    SkFixed prevRsbDelta = 0;
+
+    float penX = 0.0f;
+
+    SkPoint position;
+    SkVector tangent;
+
+    SkPathMeasure measure(*path, false);
+    float pathLength = SkScalarToFloat(measure.getLength());
+
+    if (paint->getTextAlign() != SkPaint::kLeft_Align) {
+        float textWidth = SkScalarToFloat(paint->measureText(text, len));
+        float pathOffset = pathLength;
+        if (paint->getTextAlign() == SkPaint::kCenter_Align) {
+            textWidth *= 0.5f;
+            pathOffset *= 0.5f;
+        }
+        penX += pathOffset - textWidth;
+    }
+
+    while (glyphsCount < numGlyphs && penX <= pathLength) {
+        glyph_t glyph = GET_GLYPH(text);
+
+        if (IS_END_OF_STRING(glyph)) {
+            break;
+        }
+
+        CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
+        penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
+        prevRsbDelta = cachedGlyph->mRsbDelta;
+
+        if (cachedGlyph->mIsValid) {
+            drawCachedGlyph(cachedGlyph, roundf(penX), hOffset, vOffset, measure, &position, &tangent);
+        }
+
+        penX += SkFixedToFloat(cachedGlyph->mAdvanceX);
+
+        glyphsCount++;
+    }
+}
+
 void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
         int numGlyphs, Rect *bounds) {
     if (bounds == NULL) {
@@ -208,19 +298,13 @@
     render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds, NULL);
 }
 
-#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16)
-
 void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
         int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
-        uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions) {
+        uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
     if (numGlyphs == 0 || text == NULL || len == 0) {
         return;
     }
 
-    int glyphsCount = 0;
-
-    text += start;
-
     static RenderGlyph gRenderGlyph[] = {
             &android::uirenderer::Font::drawCachedGlyph,
             &android::uirenderer::Font::drawCachedGlyphBitmap,
@@ -228,14 +312,15 @@
     };
     RenderGlyph render = gRenderGlyph[mode];
 
+    text += start;
+    int glyphsCount = 0;
+
     if (CC_LIKELY(positions == NULL)) {
         SkFixed prevRsbDelta = 0;
 
-        float penX = x;
+        float penX = x + 0.5f;
         int penY = y;
 
-        penX += 0.5f;
-
         while (glyphsCount < numGlyphs) {
             glyph_t glyph = GET_GLYPH(text);
 
@@ -245,7 +330,7 @@
             }
 
             CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
-            penX += SkFixedToFloat(SkAutoKern_AdjustF(prevRsbDelta, cachedGlyph->mLsbDelta));
+            penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
             prevRsbDelta = cachedGlyph->mRsbDelta;
 
             // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
@@ -582,6 +667,7 @@
             cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
         }
     }
+
     cachedGlyph->mIsValid = true;
 }
 
@@ -758,15 +844,9 @@
     mDrawn = true;
 }
 
-void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
-        float x2, float y2, float u2, float v2,
-        float x3, float y3, float u3, float v3,
+void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
+        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
         float x4, float y4, float u4, float v4, CacheTexture* texture) {
-
-    if (mClip &&
-            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
-        return;
-    }
     if (texture != mCurrentCacheTexture) {
         if (mCurrentQuadIndex != 0) {
             // First, draw everything stored already which uses the previous texture
@@ -802,6 +882,18 @@
     (*currentPos++) = v4;
 
     mCurrentQuadIndex++;
+}
+
+void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
+        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
+        float x4, float y4, float u4, float v4, CacheTexture* texture) {
+
+    if (mClip &&
+            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
+        return;
+    }
+
+    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
 
     if (mBounds) {
         mBounds->left = fmin(mBounds->left, x1);
@@ -816,6 +908,25 @@
     }
 }
 
+void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
+        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
+        float x4, float y4, float u4, float v4, CacheTexture* texture) {
+
+    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
+
+    if (mBounds) {
+        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
+        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
+        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
+        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
+    }
+
+    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+}
+
 uint32_t FontRenderer::getRemainingCacheCapacity() {
     uint32_t remainingCapacity = 0;
     float totalPixels = 0;
@@ -956,6 +1067,21 @@
     return mDrawn;
 }
 
+bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
+        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
+        float hOffset, float vOffset, Rect* bounds) {
+    if (!mCurrentFont) {
+        ALOGE("No font set");
+        return false;
+    }
+
+    initRender(clip, bounds);
+    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
+    finishRender();
+
+    return mDrawn;
+}
+
 void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
     // Compute gaussian weights for the blur
     // e is the euler's number
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index b767be5..4fc5862 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -24,6 +24,8 @@
 
 #include <SkScalerContext.h>
 #include <SkPaint.h>
+#include <SkPathMeasure.h>
+#include <SkPoint.h>
 
 #include <GLES2/gl2.h>
 
@@ -155,6 +157,9 @@
     void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
             int numGlyphs, int x, int y, const float* positions);
 
+    void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+            int numGlyphs, SkPath* path, float hOffset, float vOffset);
+
     /**
      * Creates a new font associated with the specified font state.
      */
@@ -200,6 +205,8 @@
     void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
             Rect* bounds, const float* pos);
+    void drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
+            SkPathMeasure& measure, SkPoint* position, SkVector* tangent);
 
     CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit);
 
@@ -244,6 +251,9 @@
     // bounds is an out parameter
     bool renderPosText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
             uint32_t len, int numGlyphs, int x, int y, const float* positions, Rect* bounds);
+    // bounds is an out parameter
+    bool renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
+            uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds);
 
     struct DropShadow {
         DropShadow() { };
@@ -305,7 +315,7 @@
     void allocateTextureMemory(CacheTexture* cacheTexture);
     void deallocateTextureMemory(CacheTexture* cacheTexture);
     void initTextTexture();
-    CacheTexture *createCacheTexture(int width, int height, bool allocate);
+    CacheTexture* createCacheTexture(int width, int height, bool allocate);
     void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
             uint32_t *retOriginX, uint32_t *retOriginY);
 
@@ -320,10 +330,18 @@
     void precacheLatin(SkPaint* paint);
 
     void issueDrawCommand();
+    void appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
+            float x2, float y2, float u2, float v2,
+            float x3, float y3, float u3, float v3,
+            float x4, float y4, float u4, float v4, CacheTexture* texture);
     void appendMeshQuad(float x1, float y1, float u1, float v1,
             float x2, float y2, float u2, float v2,
             float x3, float y3, float u3, float v3,
             float x4, float y4, float u4, float v4, CacheTexture* texture);
+    void appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
+            float x2, float y2, float u2, float v2,
+            float x3, float y3, float u3, float v3,
+            float x4, float y4, float u4, float v4, CacheTexture* texture);
 
     uint32_t mSmallCacheWidth;
     uint32_t mSmallCacheHeight;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index ebb6d88..73625b7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2300,15 +2300,6 @@
         return;
     }
 
-    float x = 0.0f;
-    float y = 0.0f;
-
-    const bool pureTranslate = mSnapshot->transform->isPureTranslate();
-    if (CC_LIKELY(pureTranslate)) {
-        x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
-        y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
-    }
-
     FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(paint);
     fontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()),
             paint->getTextSize());
@@ -2326,41 +2317,30 @@
     setupDrawShader();
     setupDrawBlending(true, mode);
     setupDrawProgram();
-    setupDrawModelView(x, y, x, y, pureTranslate, true);
+    setupDrawModelView(0.0f, 0.0f, 0.0f, 0.0f, false, true);
     setupDrawTexture(fontRenderer.getTexture(true));
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
-    setupDrawShaderUniforms(pureTranslate);
+    setupDrawShaderUniforms(false);
 
-//    mat4 pathTransform;
-//    pathTransform.loadTranslate(hOffset, vOffset, 0.0f);
-//
-//    float offset = 0.0f;
-//    SkPathMeasure pathMeasure(*path, false);
-//
-//    if (paint->getTextAlign() != SkPaint::kLeft_Align) {
-//        SkScalar pathLength = pathMeasure.getLength();
-//        if (paint->getTextAlign() == SkPaint::kCenter_Align) {
-//            pathLength = SkScalarHalf(pathLength);
-//        }
-//        offset += SkScalarToFloat(pathLength);
-//    }
+    const Rect* clip = &mSnapshot->getLocalClip();
+    Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
-//        SkScalar x;
-//        SkPath      tmp;
-//        SkMatrix    m(scaledMatrix);
-//
-//        m.postTranslate(xpos + hOffset, 0);
-//        if (matrix) {
-//            m.postConcat(*matrix);
-//        }
-//        morphpath(&tmp, *iterPath, meas, m);
-//        if (fDevice) {
-//            fDevice->drawPath(*this, tmp, iter.getPaint(), NULL, true);
-//        } else {
-//            this->drawPath(tmp, iter.getPaint(), NULL, true);
-//        }
-//    }
+#if RENDER_LAYERS_AS_REGIONS
+    const bool hasActiveLayer = hasLayer();
+#else
+    const bool hasActiveLayer = false;
+#endif
+
+    if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
+            hOffset, vOffset, hasActiveLayer ? &bounds : NULL)) {
+#if RENDER_LAYERS_AS_REGIONS
+        if (hasActiveLayer) {
+            mSnapshot->transform->mapRect(bounds);
+            dirtyLayerUnchecked(bounds, getRegion());
+        }
+#endif
+    }
 }
 
 void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
diff --git a/libs/rs/scriptc/rs_allocation.rsh b/libs/rs/scriptc/rs_allocation.rsh
index a2f69d9..89696b8 100644
--- a/libs/rs/scriptc/rs_allocation.rsh
+++ b/libs/rs/scriptc/rs_allocation.rsh
@@ -298,5 +298,67 @@
 extern uint32_t __attribute__((overloadable))
     rsElementGetVectorSize(rs_element e);
 
+/**
+ * Fetch allocation in a way described by the sampler
+ * @param a 1D allocation to sample from
+ * @param s sampler state
+ * @param location to sample from
+ */
+extern const float4 __attribute__((overloadable))
+    rsSample(rs_allocation a, rs_sampler s, float location);
+/**
+ * Fetch allocation in a way described by the sampler
+ * @param a 1D allocation to sample from
+ * @param s sampler state
+ * @param location to sample from
+ * @param lod mip level to sample from, for fractional values
+ *            mip levels will be interpolated if
+ *            RS_SAMPLER_LINEAR_MIP_LINEAR is used
+ */
+extern const float4 __attribute__((overloadable))
+    rsSample(rs_allocation a, rs_sampler s, float location, float lod);
+
+/**
+ * Fetch allocation in a way described by the sampler
+ * @param a 2D allocation to sample from
+ * @param s sampler state
+ * @param location to sample from
+ */
+extern const float4 __attribute__((overloadable))
+    rsSample(rs_allocation a, rs_sampler s, float2 location);
+
+/**
+ * Fetch allocation in a way described by the sampler
+ * @param a 2D allocation to sample from
+ * @param s sampler state
+ * @param location to sample from
+ * @param lod mip level to sample from, for fractional values
+ *            mip levels will be interpolated if
+ *            RS_SAMPLER_LINEAR_MIP_LINEAR is used
+ */
+extern const float4 __attribute__((overloadable))
+    rsSample(rs_allocation a, rs_sampler s, float2 location, float lod);
+
+/**
+ * Fetch allocation in a way described by the sampler
+ * @param a 3D allocation to sample from
+ * @param s sampler state
+ * @param location to sample from
+ */
+extern const float4 __attribute__((overloadable))
+    rsSample(rs_allocation a, rs_sampler s, float3 location);
+
+/**
+ * Fetch allocation in a way described by the sampler
+ * @param a 3D allocation to sample from
+ * @param s sampler state
+ * @param location to sample from
+ * @param lod mip level to sample from, for fractional values
+ *            mip levels will be interpolated if
+ *            RS_SAMPLER_LINEAR_MIP_LINEAR is used
+ */
+extern const float4 __attribute__((overloadable))
+    rsSample(rs_allocation a, rs_sampler s, float3 location, float lod);
+
 #endif
 
diff --git a/libs/rs/scriptc/rs_graphics.rsh b/libs/rs/scriptc/rs_graphics.rsh
index 7fdebdc..e3fde82 100644
--- a/libs/rs/scriptc/rs_graphics.rsh
+++ b/libs/rs/scriptc/rs_graphics.rsh
@@ -23,65 +23,6 @@
 #ifndef __RS_GRAPHICS_RSH__
 #define __RS_GRAPHICS_RSH__
 
-// These are API 15 once it get official
-typedef enum {
-    RS_DEPTH_FUNC_ALWAYS,
-    RS_DEPTH_FUNC_LESS,
-    RS_DEPTH_FUNC_LEQUAL,
-    RS_DEPTH_FUNC_GREATER,
-    RS_DEPTH_FUNC_GEQUAL,
-    RS_DEPTH_FUNC_EQUAL,
-    RS_DEPTH_FUNC_NOTEQUAL,
-
-    RS_DEPTH_FUNC_INVALID = 100,
-} rs_depth_func;
-
-typedef enum {
-    RS_BLEND_SRC_ZERO,                  // 0
-    RS_BLEND_SRC_ONE,                   // 1
-    RS_BLEND_SRC_DST_COLOR,             // 2
-    RS_BLEND_SRC_ONE_MINUS_DST_COLOR,   // 3
-    RS_BLEND_SRC_SRC_ALPHA,             // 4
-    RS_BLEND_SRC_ONE_MINUS_SRC_ALPHA,   // 5
-    RS_BLEND_SRC_DST_ALPHA,             // 6
-    RS_BLEND_SRC_ONE_MINUS_DST_ALPHA,   // 7
-    RS_BLEND_SRC_SRC_ALPHA_SATURATE,    // 8
-
-    RS_BLEND_SRC_INVALID = 100,
-} rs_blend_src_func;
-
-typedef enum {
-    RS_BLEND_DST_ZERO,                  // 0
-    RS_BLEND_DST_ONE,                   // 1
-    RS_BLEND_DST_SRC_COLOR,             // 2
-    RS_BLEND_DST_ONE_MINUS_SRC_COLOR,   // 3
-    RS_BLEND_DST_SRC_ALPHA,             // 4
-    RS_BLEND_DST_ONE_MINUS_SRC_ALPHA,   // 5
-    RS_BLEND_DST_DST_ALPHA,             // 6
-    RS_BLEND_DST_ONE_MINUS_DST_ALPHA,   // 7
-
-    RS_BLEND_DST_INVALID = 100,
-} rs_blend_dst_func;
-
-typedef enum {
-    RS_CULL_BACK,
-    RS_CULL_FRONT,
-    RS_CULL_NONE,
-
-    RS_CULL_INVALID = 100,
-} rs_cull_mode;
-
-typedef enum {
-    RS_SAMPLER_NEAREST,
-    RS_SAMPLER_LINEAR,
-    RS_SAMPLER_LINEAR_MIP_LINEAR,
-    RS_SAMPLER_WRAP,
-    RS_SAMPLER_CLAMP,
-    RS_SAMPLER_LINEAR_MIP_NEAREST,
-
-    RS_SAMPLER_INVALID = 100,
-} rs_sampler_value;
-
 #if (defined(RS_VERSION) && (RS_VERSION >= 14))
 /**
  * Set the color target used for all subsequent rendering calls
diff --git a/libs/rs/scriptc/rs_types.rsh b/libs/rs/scriptc/rs_types.rsh
index 5345a48..f8c2657 100644
--- a/libs/rs/scriptc/rs_types.rsh
+++ b/libs/rs/scriptc/rs_types.rsh
@@ -407,14 +407,14 @@
  *
  **/
 typedef enum {
-    RS_PRIMITIVE_POINT,
-    RS_PRIMITIVE_LINE,
-    RS_PRIMITIVE_LINE_STRIP,
-    RS_PRIMITIVE_TRIANGLE,
-    RS_PRIMITIVE_TRIANGLE_STRIP,
-    RS_PRIMITIVE_TRIANGLE_FAN,
+    RS_PRIMITIVE_POINT              = 0,
+    RS_PRIMITIVE_LINE               = 1,
+    RS_PRIMITIVE_LINE_STRIP         = 2,
+    RS_PRIMITIVE_TRIANGLE           = 3,
+    RS_PRIMITIVE_TRIANGLE_STRIP     = 4,
+    RS_PRIMITIVE_TRIANGLE_FAN       = 5,
 
-    RS_PRIMITIVE_INVALID = 100,
+    RS_PRIMITIVE_INVALID            = 100,
 } rs_primitive;
 
 /**
@@ -436,41 +436,41 @@
  * RS_* objects.  32 bit opaque handles.
  */
 typedef enum {
-    RS_TYPE_NONE,
+    RS_TYPE_NONE             = 0,
     //RS_TYPE_FLOAT_16,
-    RS_TYPE_FLOAT_32 = 2,
-    RS_TYPE_FLOAT_64,
-    RS_TYPE_SIGNED_8,
-    RS_TYPE_SIGNED_16,
-    RS_TYPE_SIGNED_32,
-    RS_TYPE_SIGNED_64,
-    RS_TYPE_UNSIGNED_8,
-    RS_TYPE_UNSIGNED_16,
-    RS_TYPE_UNSIGNED_32,
-    RS_TYPE_UNSIGNED_64,
+    RS_TYPE_FLOAT_32         = 2,
+    RS_TYPE_FLOAT_64         = 3,
+    RS_TYPE_SIGNED_8         = 4,
+    RS_TYPE_SIGNED_16        = 5,
+    RS_TYPE_SIGNED_32        = 6,
+    RS_TYPE_SIGNED_64        = 7,
+    RS_TYPE_UNSIGNED_8       = 8,
+    RS_TYPE_UNSIGNED_16      = 9,
+    RS_TYPE_UNSIGNED_32      = 10,
+    RS_TYPE_UNSIGNED_64      = 11,
 
-    RS_TYPE_BOOLEAN,
+    RS_TYPE_BOOLEAN          = 12,
 
-    RS_TYPE_UNSIGNED_5_6_5,
-    RS_TYPE_UNSIGNED_5_5_5_1,
-    RS_TYPE_UNSIGNED_4_4_4_4,
+    RS_TYPE_UNSIGNED_5_6_5   = 13,
+    RS_TYPE_UNSIGNED_5_5_5_1 = 14,
+    RS_TYPE_UNSIGNED_4_4_4_4 = 15,
 
-    RS_TYPE_MATRIX_4X4,
-    RS_TYPE_MATRIX_3X3,
-    RS_TYPE_MATRIX_2X2,
+    RS_TYPE_MATRIX_4X4       = 16,
+    RS_TYPE_MATRIX_3X3       = 17,
+    RS_TYPE_MATRIX_2X2       = 18,
 
-    RS_TYPE_ELEMENT = 1000,
-    RS_TYPE_TYPE,
-    RS_TYPE_ALLOCATION,
-    RS_TYPE_SAMPLER,
-    RS_TYPE_SCRIPT,
-    RS_TYPE_MESH,
-    RS_TYPE_PROGRAM_FRAGMENT,
-    RS_TYPE_PROGRAM_VERTEX,
-    RS_TYPE_PROGRAM_RASTER,
-    RS_TYPE_PROGRAM_STORE,
+    RS_TYPE_ELEMENT          = 1000,
+    RS_TYPE_TYPE             = 1001,
+    RS_TYPE_ALLOCATION       = 1002,
+    RS_TYPE_SAMPLER          = 1003,
+    RS_TYPE_SCRIPT           = 1004,
+    RS_TYPE_MESH             = 1005,
+    RS_TYPE_PROGRAM_FRAGMENT = 1006,
+    RS_TYPE_PROGRAM_VERTEX   = 1007,
+    RS_TYPE_PROGRAM_RASTER   = 1008,
+    RS_TYPE_PROGRAM_STORE    = 1009,
 
-    RS_TYPE_INVALID = 10000,
+    RS_TYPE_INVALID          = 10000,
 } rs_data_type;
 
 /**
@@ -482,16 +482,74 @@
  * representing texture formats.
  */
 typedef enum {
-    RS_KIND_USER,
+    RS_KIND_USER         = 0,
 
-    RS_KIND_PIXEL_L = 7,
-    RS_KIND_PIXEL_A,
-    RS_KIND_PIXEL_LA,
-    RS_KIND_PIXEL_RGB,
-    RS_KIND_PIXEL_RGBA,
-    RS_KIND_PIXEL_DEPTH,
+    RS_KIND_PIXEL_L      = 7,
+    RS_KIND_PIXEL_A      = 8,
+    RS_KIND_PIXEL_LA     = 9,
+    RS_KIND_PIXEL_RGB    = 10,
+    RS_KIND_PIXEL_RGBA   = 11,
+    RS_KIND_PIXEL_DEPTH  = 12,
 
-    RS_KIND_INVALID = 100,
+    RS_KIND_INVALID      = 100,
 } rs_data_kind;
 
+typedef enum {
+    RS_DEPTH_FUNC_ALWAYS        = 0,
+    RS_DEPTH_FUNC_LESS          = 1,
+    RS_DEPTH_FUNC_LEQUAL        = 2,
+    RS_DEPTH_FUNC_GREATER       = 3,
+    RS_DEPTH_FUNC_GEQUAL        = 4,
+    RS_DEPTH_FUNC_EQUAL         = 5,
+    RS_DEPTH_FUNC_NOTEQUAL      = 6,
+
+    RS_DEPTH_FUNC_INVALID       = 100,
+} rs_depth_func;
+
+typedef enum {
+    RS_BLEND_SRC_ZERO                   = 0,
+    RS_BLEND_SRC_ONE                    = 1,
+    RS_BLEND_SRC_DST_COLOR              = 2,
+    RS_BLEND_SRC_ONE_MINUS_DST_COLOR    = 3,
+    RS_BLEND_SRC_SRC_ALPHA              = 4,
+    RS_BLEND_SRC_ONE_MINUS_SRC_ALPHA    = 5,
+    RS_BLEND_SRC_DST_ALPHA              = 6,
+    RS_BLEND_SRC_ONE_MINUS_DST_ALPHA    = 7,
+    RS_BLEND_SRC_SRC_ALPHA_SATURATE     = 8,
+
+    RS_BLEND_SRC_INVALID                = 100,
+} rs_blend_src_func;
+
+typedef enum {
+    RS_BLEND_DST_ZERO                   = 0,
+    RS_BLEND_DST_ONE                    = 1,
+    RS_BLEND_DST_SRC_COLOR              = 2,
+    RS_BLEND_DST_ONE_MINUS_SRC_COLOR    = 3,
+    RS_BLEND_DST_SRC_ALPHA              = 4,
+    RS_BLEND_DST_ONE_MINUS_SRC_ALPHA    = 5,
+    RS_BLEND_DST_DST_ALPHA              = 6,
+    RS_BLEND_DST_ONE_MINUS_DST_ALPHA    = 7,
+
+    RS_BLEND_DST_INVALID                = 100,
+} rs_blend_dst_func;
+
+typedef enum {
+    RS_CULL_BACK     = 0,
+    RS_CULL_FRONT    = 1,
+    RS_CULL_NONE     = 2,
+
+    RS_CULL_INVALID  = 100,
+} rs_cull_mode;
+
+typedef enum {
+    RS_SAMPLER_NEAREST              = 0,
+    RS_SAMPLER_LINEAR               = 1,
+    RS_SAMPLER_LINEAR_MIP_LINEAR    = 2,
+    RS_SAMPLER_WRAP                 = 3,
+    RS_SAMPLER_CLAMP                = 4,
+    RS_SAMPLER_LINEAR_MIP_NEAREST   = 5,
+
+    RS_SAMPLER_INVALID              = 100,
+} rs_sampler_value;
+
 #endif
diff --git a/libs/utils/BlobCache.cpp b/libs/utils/BlobCache.cpp
index e52cf2f..be398ee 100644
--- a/libs/utils/BlobCache.cpp
+++ b/libs/utils/BlobCache.cpp
@@ -183,7 +183,7 @@
 status_t BlobCache::flatten(void* buffer, size_t size, int fds[], size_t count)
         const {
     if (count != 0) {
-        ALOGE("flatten: nonzero fd count: %d", count);
+        ALOGE("flatten: nonzero fd count: %zu", count);
         return BAD_VALUE;
     }
 
@@ -234,7 +234,7 @@
     mCacheEntries.clear();
 
     if (count != 0) {
-        ALOGE("unflatten: nonzero fd count: %d", count);
+        ALOGE("unflatten: nonzero fd count: %zu", count);
         return BAD_VALUE;
     }
 
diff --git a/libs/utils/Debug.cpp b/libs/utils/Debug.cpp
index f7988ec..e8ac983 100644
--- a/libs/utils/Debug.cpp
+++ b/libs/utils/Debug.cpp
@@ -199,7 +199,7 @@
     if ((int32_t)length < 0) {
         if (singleLineBytesCutoff < 0) func(cookie, "\n");
         char buf[64];
-        sprintf(buf, "(bad length: %d)", length);
+        sprintf(buf, "(bad length: %zu)", length);
         func(cookie, buf);
         return;
     }
diff --git a/libs/utils/Static.cpp b/libs/utils/Static.cpp
index bfcb2da..624e917 100644
--- a/libs/utils/Static.cpp
+++ b/libs/utils/Static.cpp
@@ -57,8 +57,8 @@
     virtual status_t writeLines(const struct iovec& vec, size_t N)
     {
         //android_writevLog(&vec, N);       <-- this is now a no-op
-        if (N != 1) ALOGI("WARNING: writeLines N=%d\n", N);
-        ALOGI("%.*s", vec.iov_len, (const char*) vec.iov_base);
+        if (N != 1) ALOGI("WARNING: writeLines N=%zu\n", N);
+        ALOGI("%.*s", (int)vec.iov_len, (const char*) vec.iov_base);
         return NO_ERROR;
     }
 };
diff --git a/libs/utils/StopWatch.cpp b/libs/utils/StopWatch.cpp
index 595aec3..b1708d6 100644
--- a/libs/utils/StopWatch.cpp
+++ b/libs/utils/StopWatch.cpp
@@ -20,6 +20,10 @@
 #include <stdlib.h>
 #include <stdio.h>
 
+/* for PRId64 */
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
 #include <utils/Log.h>
 #include <utils/Errors.h>
 #include <utils/StopWatch.h>
@@ -39,11 +43,11 @@
 {
     nsecs_t elapsed = elapsedTime();
     const int n = mNumLaps;
-    ALOGD("StopWatch %s (us): %lld ", mName, ns2us(elapsed));
+    ALOGD("StopWatch %s (us): %" PRId64 " ", mName, ns2us(elapsed));
     for (int i=0 ; i<n ; i++) {
         const nsecs_t soFar = mLaps[i].soFar;
         const nsecs_t thisLap = mLaps[i].thisLap;
-        ALOGD(" [%d: %lld, %lld]", i, ns2us(soFar), ns2us(thisLap));
+        ALOGD(" [%d: %" PRId64 ", %" PRId64, i, ns2us(soFar), ns2us(thisLap));
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 14ce266..276ca21 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -53,6 +53,7 @@
                                                  // where fade starts
     static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width
                                               // beyond which alpha->0
+    private float mMinAlpha = 0f;
 
     private float mPagingTouchSlop;
     private Callback mCallback;
@@ -120,6 +121,10 @@
                 v.getMeasuredHeight();
     }
 
+    public void setMinAlpha(float minAlpha) {
+        mMinAlpha = minAlpha;
+    }
+
     private float getAlphaForOffset(View view) {
         float viewSize = getSize(view);
         final float fadeSize = ALPHA_FADE_END * viewSize;
@@ -130,10 +135,7 @@
         } else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {
             result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
         }
-        // Make .03 alpha the minimum so you always see the item a bit-- slightly below
-        // .03, the item disappears entirely (as if alpha = 0) and that discontinuity looks
-        // a bit jarring
-        return Math.max(0.03f, result);
+        return Math.max(mMinAlpha, result);
     }
 
     // invalidate the view's own bounds all the way up the view hierarchy
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
index 76e6ee8..97c9553 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
@@ -44,7 +44,7 @@
 import java.util.ArrayList;
 
 public class RecentsHorizontalScrollView extends HorizontalScrollView
-    implements SwipeHelper.Callback {
+        implements SwipeHelper.Callback, RecentsPanelView.RecentsScrollView {
     private static final String TAG = RecentsPanelView.TAG;
     private static final boolean DEBUG = RecentsPanelView.DEBUG;
     private LinearLayout mLinearLayout;
@@ -65,6 +65,10 @@
         mRecycledViews = new ArrayList<View>();
     }
 
+    public void setMinSwipeAlpha(float minAlpha) {
+        mSwipeHelper.setMinAlpha(minAlpha);
+    }
+
     private int scrollPositionOfMostRecent() {
         return mLinearLayout.getWidth() - getWidth();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 18132ff..61aaa43 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -92,6 +92,13 @@
         public void onRecentsPanelVisibilityChanged(boolean visible);
     }
 
+    public static interface RecentsScrollView {
+        public int numItemsInOneScreenful();
+        public void setAdapter(TaskDescriptionAdapter adapter);
+        public void setCallback(RecentsCallback callback);
+        public void setMinSwipeAlpha(float minAlpha);
+    }
+
     private final class OnLongClickDelegate implements View.OnLongClickListener {
         View mOtherView;
         OnLongClickDelegate(View other) {
@@ -196,16 +203,11 @@
     }
 
     public int numItemsInOneScreenful() {
-        if (mRecentsContainer instanceof RecentsHorizontalScrollView){
-            RecentsHorizontalScrollView scrollView
-                    = (RecentsHorizontalScrollView) mRecentsContainer;
+        if (mRecentsContainer instanceof RecentsScrollView){
+            RecentsScrollView scrollView
+                    = (RecentsScrollView) mRecentsContainer;
             return scrollView.numItemsInOneScreenful();
-        } else if (mRecentsContainer instanceof RecentsVerticalScrollView){
-            RecentsVerticalScrollView scrollView
-                    = (RecentsVerticalScrollView) mRecentsContainer;
-            return scrollView.numItemsInOneScreenful();
-        }
-        else {
+        }  else {
             throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
         }
     }
@@ -427,18 +429,12 @@
         mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container);
         mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy);
         mListAdapter = new TaskDescriptionAdapter(mContext);
-        if (mRecentsContainer instanceof RecentsHorizontalScrollView){
-            RecentsHorizontalScrollView scrollView
-                    = (RecentsHorizontalScrollView) mRecentsContainer;
+        if (mRecentsContainer instanceof RecentsScrollView){
+            RecentsScrollView scrollView
+                    = (RecentsScrollView) mRecentsContainer;
             scrollView.setAdapter(mListAdapter);
             scrollView.setCallback(this);
-        } else if (mRecentsContainer instanceof RecentsVerticalScrollView){
-            RecentsVerticalScrollView scrollView
-                    = (RecentsVerticalScrollView) mRecentsContainer;
-            scrollView.setAdapter(mListAdapter);
-            scrollView.setCallback(this);
-        }
-        else {
+        } else {
             throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
         }
 
@@ -467,6 +463,14 @@
         };
     }
 
+    public void setMinSwipeAlpha(float minAlpha) {
+        if (mRecentsContainer instanceof RecentsScrollView){
+            RecentsScrollView scrollView
+                = (RecentsScrollView) mRecentsContainer;
+            scrollView.setMinSwipeAlpha(minAlpha);
+        }
+    }
+
     private void createCustomAnimations(LayoutTransition transitioner) {
         transitioner.setDuration(200);
         transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
@@ -523,8 +527,7 @@
         synchronized (td) {
             if (mRecentsContainer != null) {
                 ViewGroup container = mRecentsContainer;
-                if (container instanceof HorizontalScrollView
-                        || container instanceof ScrollView) {
+                if (container instanceof RecentsScrollView) {
                     container = (ViewGroup) container.findViewById(
                             R.id.recents_linear_layout);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
index 1bfd000..f4e516c 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
@@ -43,7 +43,8 @@
 
 import java.util.ArrayList;
 
-public class RecentsVerticalScrollView extends ScrollView implements SwipeHelper.Callback {
+public class RecentsVerticalScrollView extends ScrollView
+        implements SwipeHelper.Callback, RecentsPanelView.RecentsScrollView {
     private static final String TAG = RecentsPanelView.TAG;
     private static final boolean DEBUG = RecentsPanelView.DEBUG;
     private LinearLayout mLinearLayout;
@@ -65,6 +66,10 @@
         mRecycledViews = new ArrayList<View>();
     }
 
+    public void setMinSwipeAlpha(float minAlpha) {
+        mSwipeHelper.setMinAlpha(minAlpha);
+    }
+
     private int scrollPositionOfMostRecent() {
         return mLinearLayout.getHeight() - getHeight();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index b2d9e64..2e1f120 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -432,6 +432,11 @@
         mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL,
                 mRecentsPanel));
         mRecentsPanel.setVisibility(View.GONE);
+
+        // Make .03 alpha the minimum so you always see the item a bit-- slightly below
+        // .03, the item disappears entirely (as if alpha = 0) and that discontinuity looks
+        // a bit jarring
+        mRecentsPanel.setMinSwipeAlpha(0.03f);
         WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams());
 
         WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index c59290c..95704f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -203,7 +203,7 @@
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         Handler handler = new WifiHandler();
         mWifiChannel = new AsyncChannel();
-        Messenger wifiMessenger = mWifiManager.getMessenger();
+        Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
         if (wifiMessenger != null) {
             mWifiChannel.connect(mContext, handler, wifiMessenger);
         }
@@ -767,17 +767,10 @@
             } else if (!mWifiConnected) {
                 mWifiSsid = null;
             }
-            // Apparently the wifi level is not stable at this point even if we've just connected to
-            // the network; we need to wait for an RSSI_CHANGED_ACTION for that. So let's just set
-            // it to 0 for now
-            mWifiLevel = 0;
-            mWifiRssi = -200;
         } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
-            if (mWifiConnected) {
-                mWifiRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
-                mWifiLevel = WifiManager.calculateSignalLevel(
-                        mWifiRssi, WifiIcons.WIFI_LEVEL_COUNT);
-            }
+            mWifiRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
+            mWifiLevel = WifiManager.calculateSignalLevel(
+                    mWifiRssi, WifiIcons.WIFI_LEVEL_COUNT);
         }
 
         updateWifiIcons();
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java
index 3384661..edf5199 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/LockScreen.java
@@ -231,7 +231,8 @@
                 if (!mCameraDisabled) {
                     // Start the Camera
                     Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
-                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                            | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                     try {
                         ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
                     } catch (RemoteException e) {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 5208785..f09c43f 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -914,7 +914,7 @@
      * Get a reference to handler. This is used by a client to establish
      * an AsyncChannel communication with WifiService
      */
-    public Messenger getMessenger() {
+    public Messenger getWifiServiceMessenger() {
         /* Enforce the highest permissions
            TODO: when we consider exposing the asynchronous API, think about
                  how to provide both access and change permissions seperately
@@ -924,6 +924,13 @@
         return new Messenger(mAsyncServiceHandler);
     }
 
+    /** Get a reference to WifiStateMachine handler for AsyncChannel communication */
+    public Messenger getWifiStateMachineMessenger() {
+        enforceAccessPermission();
+        enforceChangePermission();
+        return mWifiStateMachine.getMessenger();
+    }
+
     /**
      * Get the IP and proxy configuration file
      */
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 9573fda..88a0ccb 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -1215,6 +1215,8 @@
                 return retValue;
             }
             protected boolean turnOffUpstreamMobileConnection() {
+                // ignore pending renewal requests
+                ++mCurrentConnectionSequence;
                 if (mMobileApnReserved != ConnectivityManager.TYPE_NONE) {
                     try {
                         mConnService.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
@@ -1304,6 +1306,14 @@
                 if (upType == ConnectivityManager.TYPE_MOBILE_DUN ||
                         upType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
                     turnOnUpstreamMobileConnection(upType);
+                } else if (upType != ConnectivityManager.TYPE_NONE) {
+                    /* If we've found an active upstream connection that's not DUN/HIPRI
+                     * we should stop any outstanding DUN/HIPRI start requests.
+                     *
+                     * If we found NONE we don't want to do this as we want any previous
+                     * requests to keep trying to bring up something we can use.
+                     */
+                    turnOffUpstreamMobileConnection();
                 }
 
                 if (upType == ConnectivityManager.TYPE_NONE) {
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java
index b798ee7..9849e3c 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java
@@ -21,18 +21,21 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Path;
+import android.graphics.PathMeasure;
 import android.os.Bundle;
 import android.view.View;
 
 @SuppressWarnings({"UnusedDeclaration"})
 public class TextOnPathActivity extends Activity {
     private Path mPath;
+    private Path mStraightPath;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         mPath = makePath();
+        mStraightPath = makeStraightPath();
 
         final TextOnPathView view = new TextOnPathView(this);
         setContentView(view);
@@ -51,11 +54,28 @@
         path.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f);
     }
 
+    private Path makeStraightPath() {
+        Path path = new Path();
+        buildStraightPath(path);
+        return path;
+    }
+
+    private void buildStraightPath(Path path) {
+        path.moveTo(0.0f, 0.0f);
+        path.lineTo(400.0f, 0.0f);
+    }
+
     public class TextOnPathView extends View {
         private static final String TEST_STRING = "Hello OpenGL renderer, text on path! ";
 
         private final Paint mPaint;
+        private final Paint mPathPaint;
         private final String mText;
+        private final PathMeasure mMeasure;
+        private final float mLength;
+        private final float[] mLines;
+        private final float[] mPos;
+        private final float[] mTan;
 
         public TextOnPathView(Context c) {
             super(c);
@@ -64,11 +84,23 @@
             mPaint.setAntiAlias(true);
             mPaint.setColor(0xff000000);
 
+            mPathPaint = new Paint();
+            mPathPaint.setAntiAlias(true);
+            mPathPaint.setStyle(Paint.Style.STROKE);
+            mPathPaint.setColor(0xff000099);
+
             StringBuilder builder = new StringBuilder(TEST_STRING.length() * 2);
             for (int i = 0; i < 2; i++) {
                 builder.append(TEST_STRING);
             }
             mText = builder.toString();
+
+            mMeasure = new PathMeasure(mPath, false);
+            mLength = mMeasure.getLength();
+            
+            mLines = new float[100 * 4];
+            mPos = new float[2];
+            mTan = new float[2];
         }
 
         @Override
@@ -81,19 +113,40 @@
             canvas.translate(400.0f, 350.0f);
             mPaint.setTextAlign(Paint.Align.LEFT);
             canvas.drawTextOnPath(mText + mText, mPath, 0.0f, 0.0f, mPaint);
+            canvas.drawPath(mPath, mPathPaint);
+            
+            for (int i = 0; i < 100; i++) {
+                mMeasure.getPosTan(i * mLength / 100.0f, mPos, mTan);
+                mLines[i * 4    ] = mPos[0];
+                mLines[i * 4 + 1] = mPos[1];
+                mLines[i * 4 + 2] = mPos[0] + mTan[1] * 15;
+                mLines[i * 4 + 3] = mPos[1] - mTan[0] * 15;
+            }
+            canvas.drawLines(mLines, mPathPaint);
+            
+            canvas.translate(200.0f, 0.0f);
+            canvas.drawTextOnPath(mText + mText, mStraightPath, 0.0f, 0.0f, mPaint);
+            canvas.drawPath(mStraightPath, mPathPaint);
+
             canvas.restore();
 
             canvas.save();
             canvas.translate(150.0f, 60.0f);
-            canvas.drawTextOnPath(mText, mPath, 0.0f, 0.0f, mPaint);
+            canvas.drawTextOnPath(mText, mPath, 0.0f, 10.0f, mPaint);
+            mMeasure.getPosTan(5.0f, mPos, mTan);
+            canvas.drawLine(mPos[0], mPos[1], mPos[0] + mTan[1] * 10, mPos[1] - mTan[0] * 10,
+                    mPathPaint);
+            canvas.drawPath(mPath, mPathPaint);
 
             canvas.translate(250.0f, 0.0f);
             mPaint.setTextAlign(Paint.Align.CENTER);
             canvas.drawTextOnPath(mText, mPath, 0.0f, 0.0f, mPaint);
+            canvas.drawPath(mPath, mPathPaint);
 
             canvas.translate(250.0f, 0.0f);
             mPaint.setTextAlign(Paint.Align.RIGHT);
             canvas.drawTextOnPath(mText, mPath, 0.0f, 0.0f, mPaint);
+            canvas.drawPath(mPath, mPathPaint);
             canvas.restore();
         }
     }
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 61dfebf..6b08074 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -101,7 +101,9 @@
 
     void clearBlacklist();
 
-    Messenger getMessenger();
+    Messenger getWifiServiceMessenger();
+
+    Messenger getWifiStateMachineMessenger();
 
     String getConfigFile();
 }
diff --git a/wifi/java/android/net/wifi/SupplicantState.java b/wifi/java/android/net/wifi/SupplicantState.java
index 509b02c..4a2037d 100644
--- a/wifi/java/android/net/wifi/SupplicantState.java
+++ b/wifi/java/android/net/wifi/SupplicantState.java
@@ -171,8 +171,8 @@
     }
 
 
-    /* Supplicant associating or authenticating is considered a handshake state */
-    static boolean isHandshakeState(SupplicantState state) {
+    /** Supplicant associating or authenticating is considered a handshake state {@hide} */
+    public static boolean isHandshakeState(SupplicantState state) {
         switch(state) {
             case AUTHENTICATING:
             case ASSOCIATING:
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1a0e0da..1acfd3a 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1096,7 +1096,7 @@
      * @hide
      */
      public void asyncConnect(Context srcContext, Handler srcHandler) {
-        mAsyncChannel.connect(srcContext, srcHandler, getMessenger());
+        mAsyncChannel.connect(srcContext, srcHandler, getWifiServiceMessenger());
      }
 
     /**
@@ -1197,15 +1197,30 @@
      * @return Messenger pointing to the WifiService handler
      * @hide
      */
-    public Messenger getMessenger() {
+    public Messenger getWifiServiceMessenger() {
         try {
-            return mService.getMessenger();
+            return mService.getWifiServiceMessenger();
         } catch (RemoteException e) {
             return null;
         }
     }
 
     /**
+     * Get a reference to WifiStateMachine handler.
+     * @return Messenger pointing to the WifiService handler
+     * @hide
+     */
+    public Messenger getWifiStateMachineMessenger() {
+        try {
+            return mService.getWifiStateMachineMessenger();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+
+
+    /**
      * Returns the file in which IP and proxy configuration data is stored
      * @hide
      */
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 1b64f3e..e140d80 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -463,8 +463,12 @@
     private State mScanModeState = new ScanModeState();
     /* Connecting to an access point */
     private State mConnectModeState = new ConnectModeState();
-    /* Fetching IP after network connection (assoc+auth complete) */
-    private State mConnectingState = new ConnectingState();
+    /* Connected at 802.11 (L2) level */
+    private State mL2ConnectedState = new L2ConnectedState();
+    /* fetching IP after connection to access point (assoc+auth complete) */
+    private State mObtainingIpState = new ObtainingIpState();
+    /* Waiting for link quality verification to be complete */
+    private State mVerifyingLinkState = new VerifyingLinkState();
     /* Connected with IP addr */
     private State mConnectedState = new ConnectedState();
     /* disconnect issued, waiting for network disconnect confirmation */
@@ -629,8 +633,10 @@
                 addState(mDriverStartedState, mSupplicantStartedState);
                     addState(mScanModeState, mDriverStartedState);
                     addState(mConnectModeState, mDriverStartedState);
-                        addState(mConnectingState, mConnectModeState);
-                        addState(mConnectedState, mConnectModeState);
+                        addState(mL2ConnectedState, mConnectModeState);
+                            addState(mObtainingIpState, mL2ConnectedState);
+                            addState(mVerifyingLinkState, mL2ConnectedState);
+                            addState(mConnectedState, mL2ConnectedState);
                         addState(mDisconnectingState, mConnectModeState);
                         addState(mDisconnectedState, mConnectModeState);
                         addState(mWaitForWpsCompletionState, mConnectModeState);
@@ -655,6 +661,9 @@
      * Methods exposed for public use
      ********************************************************/
 
+    public Messenger getMessenger() {
+        return new Messenger(getHandler());
+    }
     /**
      * TODO: doc
      */
@@ -1543,12 +1552,14 @@
         Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                 | Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);
+        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
         intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties (mLinkProperties));
         if (bssid != null)
             intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
-        if (mNetworkInfo.getState() == NetworkInfo.State.CONNECTED)
+        if (mNetworkInfo.getDetailedState() == DetailedState.VERIFYING_POOR_LINK ||
+                mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) {
             intent.putExtra(WifiManager.EXTRA_WIFI_INFO, new WifiInfo(mWifiInfo));
+        }
         mContext.sendStickyBroadcast(intent);
     }
 
@@ -1740,9 +1751,6 @@
             }
         } else {
             configureLinkProperties();
-            setNetworkDetailedState(DetailedState.CONNECTED);
-            mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
-            sendNetworkStateChangeBroadcast(mLastBssid);
         }
     }
 
@@ -1890,6 +1898,8 @@
                 case CMD_SET_AP_CONFIG_COMPLETED:
                 case CMD_REQUEST_AP_CONFIG:
                 case CMD_RESPONSE_AP_CONFIG:
+                case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
+                case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
                     break;
                 case WifiMonitor.DRIVER_HUNG_EVENT:
                     setWifiEnabled(false);
@@ -2885,7 +2895,7 @@
                     /* send event to CM & network change broadcast */
                     setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);
                     sendNetworkStateChangeBroadcast(mLastBssid);
-                    transitionTo(mConnectingState);
+                    transitionTo(mObtainingIpState);
                     break;
                 case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                     if (DBG) log("Network connection lost");
@@ -2900,122 +2910,18 @@
         }
     }
 
-    class ConnectingState extends State {
 
-        @Override
-        public void enter() {
-            if (DBG) log(getName() + "\n");
-            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
-
-            try {
-                mNwService.enableIpv6(mInterfaceName);
-            } catch (RemoteException re) {
-                loge("Failed to enable IPv6: " + re);
-            } catch (IllegalStateException e) {
-                loge("Failed to enable IPv6: " + e);
-            }
-
-            if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
-                //start DHCP
-                mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
-                        mContext, WifiStateMachine.this, mInterfaceName);
-                mDhcpStateMachine.registerForPreDhcpNotification();
-                mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
-            } else {
-                DhcpInfoInternal dhcpInfoInternal = mWifiConfigStore.getIpConfiguration(
-                        mLastNetworkId);
-                InterfaceConfiguration ifcg = new InterfaceConfiguration();
-                ifcg.setLinkAddress(dhcpInfoInternal.makeLinkAddress());
-                ifcg.setInterfaceUp();
-                try {
-                    mNwService.setInterfaceConfig(mInterfaceName, ifcg);
-                    if (DBG) log("Static IP configuration succeeded");
-                    sendMessage(CMD_STATIC_IP_SUCCESS, dhcpInfoInternal);
-                } catch (RemoteException re) {
-                    loge("Static IP configuration failed: " + re);
-                    sendMessage(CMD_STATIC_IP_FAILURE);
-                } catch (IllegalStateException e) {
-                    loge("Static IP configuration failed: " + e);
-                    sendMessage(CMD_STATIC_IP_FAILURE);
-                }
-            }
-        }
-      @Override
-      public boolean processMessage(Message message) {
-          if (DBG) log(getName() + message.toString() + "\n");
-
-          switch(message.what) {
-              case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
-                  handlePreDhcpSetup();
-                  mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE);
-                  break;
-              case DhcpStateMachine.CMD_POST_DHCP_ACTION:
-                  handlePostDhcpSetup();
-                  if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
-                      handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj);
-                      transitionTo(mConnectedState);
-                  } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {
-                      handleFailedIpConfiguration();
-                      transitionTo(mDisconnectingState);
-                  }
-                  break;
-              case CMD_STATIC_IP_SUCCESS:
-                  handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj);
-                  transitionTo(mConnectedState);
-                  break;
-              case CMD_STATIC_IP_FAILURE:
-                  handleFailedIpConfiguration();
-                  transitionTo(mDisconnectingState);
-                  break;
-              case CMD_DISCONNECT:
-                  mWifiNative.disconnect();
-                  transitionTo(mDisconnectingState);
-                  break;
-                  /* Ignore connection to same network */
-              case CMD_CONNECT_NETWORK:
-                  int netId = message.arg1;
-                  if (mWifiInfo.getNetworkId() == netId) {
-                      break;
-                  }
-                  return NOT_HANDLED;
-              case CMD_SAVE_NETWORK:
-                  deferMessage(message);
-                  break;
-                  /* Ignore */
-              case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                  break;
-              case CMD_SET_SCAN_MODE:
-                  if (message.arg1 == SCAN_ONLY_MODE) {
-                      sendMessage(CMD_DISCONNECT);
-                      deferMessage(message);
-                  }
-                  break;
-                  /* Defer scan when IP is being fetched */
-              case CMD_START_SCAN:
-                  deferMessage(message);
-                  break;
-                  /* Defer any power mode changes since we must keep active power mode at DHCP */
-              case CMD_SET_HIGH_PERF_MODE:
-                  deferMessage(message);
-                  break;
-              default:
-                return NOT_HANDLED;
-          }
-          EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
-          return HANDLED;
-      }
-    }
-
-    class ConnectedState extends State {
+    class L2ConnectedState extends State {
         @Override
         public void enter() {
             if (DBG) log(getName() + "\n");
             EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
             mRssiPollToken++;
             if (mEnableRssiPolling) {
-                sendMessage(obtainMessage(WifiStateMachine.CMD_RSSI_POLL, mRssiPollToken, 0));
+                sendMessage(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0));
             }
         }
+
         @Override
         public boolean processMessage(Message message) {
             if (DBG) log(getName() + message.toString() + "\n");
@@ -3028,8 +2934,11 @@
               case DhcpStateMachine.CMD_POST_DHCP_ACTION:
                   handlePostDhcpSetup();
                   if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
+                      if (DBG) log("DHCP successful");
                       handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj);
+                      transitionTo(mVerifyingLinkState);
                   } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {
+                      if (DBG) log("DHCP failed");
                       handleFailedIpConfiguration();
                       transitionTo(mDisconnectingState);
                   }
@@ -3067,7 +2976,7 @@
                     if (mWifiInfo.getNetworkId() == result.getNetworkId()) {
                         if (result.hasIpChanged()) {
                             log("Reconfiguring IP on connection");
-                            transitionTo(mConnectingState);
+                            transitionTo(mObtainingIpState);
                         }
                         if (result.hasProxyChanged()) {
                             log("Reconfiguring proxy on connection");
@@ -3084,7 +2993,7 @@
                     if (message.arg1 == mRssiPollToken) {
                         // Get Info and continue polling
                         fetchRssiAndLinkSpeedNative();
-                        sendMessageDelayed(obtainMessage(WifiStateMachine.CMD_RSSI_POLL,
+                        sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
                                 mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
                     } else {
                         // Polling has completed
@@ -3096,25 +3005,22 @@
                     if (mEnableRssiPolling) {
                         // first poll
                         fetchRssiAndLinkSpeedNative();
-                        sendMessageDelayed(obtainMessage(WifiStateMachine.CMD_RSSI_POLL,
+                        sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
                                 mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
                     }
                     break;
                 default:
                     return NOT_HANDLED;
             }
+
             if (eventLoggingEnabled) {
                 EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
             }
             return HANDLED;
         }
+
         @Override
         public void exit() {
-
-            /* Request a CS wakelock during transition to mobile */
-            checkAndSetConnectivityInstance();
-            mCm.requestNetworkTransitionWakelock(TAG);
-
             /* If a scan result is pending in connected state, the supplicant
              * is in SCAN_ONLY_MODE. Restore CONNECT_MODE on exit
              */
@@ -3124,6 +3030,141 @@
         }
     }
 
+    class ObtainingIpState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log(getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
+                //start DHCP
+                mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
+                        mContext, WifiStateMachine.this, mInterfaceName);
+                mDhcpStateMachine.registerForPreDhcpNotification();
+                mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
+            } else {
+                DhcpInfoInternal dhcpInfoInternal = mWifiConfigStore.getIpConfiguration(
+                        mLastNetworkId);
+                InterfaceConfiguration ifcg = new InterfaceConfiguration();
+                ifcg.setLinkAddress(dhcpInfoInternal.makeLinkAddress());
+                ifcg.setInterfaceUp();
+                try {
+                    mNwService.setInterfaceConfig(mInterfaceName, ifcg);
+                    if (DBG) log("Static IP configuration succeeded");
+                    sendMessage(CMD_STATIC_IP_SUCCESS, dhcpInfoInternal);
+                } catch (RemoteException re) {
+                    loge("Static IP configuration failed: " + re);
+                    sendMessage(CMD_STATIC_IP_FAILURE);
+                } catch (IllegalStateException e) {
+                    loge("Static IP configuration failed: " + e);
+                    sendMessage(CMD_STATIC_IP_FAILURE);
+                }
+            }
+        }
+      @Override
+      public boolean processMessage(Message message) {
+          if (DBG) log(getName() + message.toString() + "\n");
+          switch(message.what) {
+            case CMD_STATIC_IP_SUCCESS:
+                  handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj);
+                  transitionTo(mVerifyingLinkState);
+                  break;
+              case CMD_STATIC_IP_FAILURE:
+                  handleFailedIpConfiguration();
+                  transitionTo(mDisconnectingState);
+                  break;
+             case CMD_SAVE_NETWORK:
+                  deferMessage(message);
+                  break;
+                  /* Defer any power mode changes since we must keep active power mode at DHCP */
+              case CMD_SET_HIGH_PERF_MODE:
+                  deferMessage(message);
+                  break;
+              default:
+                  return NOT_HANDLED;
+          }
+          EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+          return HANDLED;
+      }
+    }
+
+    class VerifyingLinkState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log(getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+            setNetworkDetailedState(DetailedState.VERIFYING_POOR_LINK);
+            mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.VERIFYING_POOR_LINK);
+            sendNetworkStateChangeBroadcast(mLastBssid);
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
+                    //stay here
+                    break;
+                case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
+                    try {
+                        mNwService.enableIpv6(mInterfaceName);
+                    } catch (RemoteException re) {
+                        loge("Failed to enable IPv6: " + re);
+                    } catch (IllegalStateException e) {
+                        loge("Failed to enable IPv6: " + e);
+                    }
+
+                    setNetworkDetailedState(DetailedState.CONNECTED);
+                    mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
+                    sendNetworkStateChangeBroadcast(mLastBssid);
+                    transitionTo(mConnectedState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class ConnectedState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log(getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+       }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) log(getName() + message.toString() + "\n");
+            switch (message.what) {
+               case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
+                    if (DBG) log("Watchdog reports poor link");
+                    try {
+                        mNwService.disableIpv6(mInterfaceName);
+                    } catch (RemoteException re) {
+                        loge("Failed to disable IPv6: " + re);
+                    } catch (IllegalStateException e) {
+                        loge("Failed to disable IPv6: " + e);
+                    }
+                    /* Report a disconnect */
+                    setNetworkDetailedState(DetailedState.DISCONNECTED);
+                    mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED);
+                    sendNetworkStateChangeBroadcast(mLastBssid);
+
+                    transitionTo(mVerifyingLinkState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+        @Override
+        public void exit() {
+            /* Request a CS wakelock during transition to mobile */
+            checkAndSetConnectivityInstance();
+            mCm.requestNetworkTransitionWakelock(TAG);
+        }
+    }
+
     class DisconnectingState extends State {
         @Override
         public void enter() {
diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
index 0ca3852..a2f6343 100644
--- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
@@ -26,9 +26,12 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.net.arp.ArpPeer;
 import android.net.ConnectivityManager;
-import android.net.DnsPinger;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.NetworkInfo;
+import android.net.RouteInfo;
 import android.net.Uri;
 import android.os.Message;
 import android.os.SystemClock;
@@ -38,6 +41,7 @@
 import android.util.Log;
 
 import com.android.internal.R;
+import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -46,49 +50,66 @@
 import java.io.PrintWriter;
 import java.net.HttpURLConnection;
 import java.net.InetAddress;
+import java.net.SocketException;
 import java.net.URL;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
 
 /**
- * {@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.
+ * WifiWatchdogStateMachine monitors the connection to a Wi-Fi
+ * network. After the framework notifies that it has connected to an
+ * acccess point and is waiting for link to be verified, the watchdog
+ * takes over and verifies if the link is good by doing ARP pings to
+ * the gateway using {@link ArpPeer}.
+ *
+ * Upon successful verification, the watchdog notifies and continues
+ * to monitor the link afterwards when the RSSI level falls below
+ * a certain threshold.
+
+ * When Wi-fi connects at L2 layer, the beacons from access point reach
+ * the device and it can maintain a connection, but the application
+ * connectivity can be flaky (due to bigger packet size exchange).
+ *
+ * We now monitor the quality of the last hop on
+ * Wi-Fi using signal strength and ARP connectivity as indicators
+ * to decide if the link is good enough to switch to Wi-Fi as the uplink.
+ *
+ * ARP pings are useful for link validation but can still get through
+ * when the application traffic fails to go through and are thus not
+ * the best indicator of real packet loss since they are tiny packets
+ * (28 bytes) and have a much low chance of packet corruption than the
+ * regular data packets.
+ *
+ * When signal strength and ARP are used together, it ends up working well in tests.
+ * The goal is to switch to Wi-Fi after validating ARP transfer
+ * and RSSI and then switching out of Wi-Fi when we hit a low
+ * signal strength threshold and then waiting until the signal strength
+ * improves and validating ARP transfer.
  *
  * @hide
  */
 public class WifiWatchdogStateMachine extends StateMachine {
 
-    private static final boolean DBG = false;
+    /* STOPSHIP: Keep this configurable for debugging until ship */
+    private static boolean DBG = false;
     private static final String TAG = "WifiWatchdogStateMachine";
-    private static final String DISABLED_NETWORK_NOTIFICATION_ID = "WifiWatchdog.networkdisabled";
     private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden";
 
-    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;
+    /* Wi-fi connection is considered poor below this
+       RSSI level threshold and the watchdog report it
+       to the WifiStateMachine */
+    private static final int RSSI_LEVEL_CUTOFF = 1;
+    /* Wi-fi connection is monitored actively below this
+       threshold */
+    private static final int RSSI_LEVEL_MONITOR = 2;
 
-    private static final long DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS = 2 * 60 * 1000;
-    private static final long DEFAULT_DNS_CHECK_LONG_INTERVAL_MS = 60 * 60 * 1000;
+    private int mCurrentSignalLevel;
+
+    private static final long DEFAULT_ARP_CHECK_INTERVAL_MS = 2 * 60 * 1000;
     private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000;
 
-    private static final int DEFAULT_MAX_SSID_BLACKLISTS = 7;
-    private static final int DEFAULT_NUM_DNS_PINGS = 5; // Multiple pings to detect setup issues
-    private static final int DEFAULT_MIN_DNS_RESPONSES = 1;
+    private static final int DEFAULT_NUM_ARP_PINGS = 5;
+    private static final int DEFAULT_MIN_ARP_RESPONSES = 1;
 
-    private static final int DEFAULT_DNS_PING_TIMEOUT_MS = 2000;
-
-    private static final long DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS = 15 * 1000;
+    private static final int DEFAULT_ARP_PING_TIMEOUT_MS = 100;
 
     // See http://go/clientsdns for usage approval
     private static final String DEFAULT_WALLED_GARDEN_URL =
@@ -102,10 +123,6 @@
      */
     private static final int WALLED_GARDEN_START_DELAY_MS = 3000;
 
-    private static final int DNS_INTRATEST_PING_INTERVAL_MS = 200;
-    /* With some router setups, it takes a few hunder milli-seconds before connection is active */
-    private static final int DNS_START_DELAY_MS = 1000;
-
     private static final int BASE = Protocol.BASE_WIFI_WATCHDOG;
 
     /**
@@ -118,99 +135,76 @@
      * 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]
-     */
+    /* Passed with RSSI information */
     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 EVENT_WATCHDOG_SETTINGS_CHANGE         = BASE + 6;
 
-    private static final int MESSAGE_HANDLE_WALLED_GARDEN           = BASE + 100;
-    private static final int MESSAGE_HANDLE_BAD_AP                  = BASE + 101;
-    /**
-     * arg1 == mOnlineWatchState.checkCount
-     */
-    private static final int MESSAGE_SINGLE_DNS_CHECK               = BASE + 102;
-    private static final int MESSAGE_NETWORK_FOLLOWUP               = BASE + 103;
-    private static final int MESSAGE_DELAYED_WALLED_GARDEN_CHECK    = BASE + 104;
+    /* Internal messages */
+    private static final int CMD_ARP_CHECK                          = BASE + 11;
+    private static final int CMD_DELAYED_WALLED_GARDEN_CHECK        = BASE + 12;
+
+    /* Notifications to WifiStateMachine */
+    static final int POOR_LINK_DETECTED                             = BASE + 21;
+    static final int GOOD_LINK_DETECTED                             = BASE + 22;
+
+    private static final int SINGLE_ARP_CHECK = 0;
+    private static final int FULL_ARP_CHECK   = 1;
 
     private Context mContext;
     private ContentResolver mContentResolver;
     private WifiManager mWifiManager;
-    private DnsPinger mDnsPinger;
     private IntentFilter mIntentFilter;
     private BroadcastReceiver mBroadcastReceiver;
+    private AsyncChannel mWsmChannel = new AsyncChannel();;
 
     private DefaultState mDefaultState = new DefaultState();
     private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState();
     private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState();
     private NotConnectedState mNotConnectedState = new NotConnectedState();
+    private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState();
     private ConnectedState mConnectedState = new ConnectedState();
-    private DnsCheckingState mDnsCheckingState = new DnsCheckingState();
+    private WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState();
+    /* Online and watching link connectivity */
     private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
+    /* Online and doing nothing */
     private OnlineState mOnlineState = new OnlineState();
-    private DnsCheckFailureState mDnsCheckFailureState = new DnsCheckFailureState();
-    private DelayWalledGardenState mDelayWalledGardenState = new DelayWalledGardenState();
-    private WalledGardenState mWalledGardenState = new WalledGardenState();
-    private BlacklistedApState mBlacklistedApState = new BlacklistedApState();
 
-    private long mDnsCheckShortIntervalMs;
-    private long mDnsCheckLongIntervalMs;
+    private int mArpToken = 0;
+    private long mArpCheckIntervalMs;
     private long mWalledGardenIntervalMs;
-    private int mMaxSsidBlacklists;
-    private int mNumDnsPings;
-    private int mMinDnsResponses;
-    private int mDnsPingTimeoutMs;
-    private long mBlacklistFollowupIntervalMs;
+    private int mNumArpPings;
+    private int mMinArpResponses;
+    private int mArpPingTimeoutMs;
     private boolean mPoorNetworkDetectionEnabled;
     private boolean mWalledGardenTestEnabled;
     private String mWalledGardenUrl;
 
-    private boolean mShowDisabledNotification;
-    /**
-     * The {@link WifiInfo} object passed to WWSM on network broadcasts
-     */
-    private WifiInfo mConnectionInfo;
-    private int mNetEventCounter = 0;
+    private WifiInfo mWifiInfo;
+    private LinkProperties mLinkProperties;
 
-    /**
-     * Currently maintained but not used, TODO
-     */
-    private HashSet<String> mBssids = new HashSet<String>();
-    private int mNumCheckFailures = 0;
+    private long mLastWalledGardenCheckTime = 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;
     private static boolean sWifiOnly = false;
-    private boolean mDisabledNotificationShown;
     private boolean mWalledGardenNotificationShown;
-    public boolean mHasConnectedWifiManager = false;
 
     /**
      * STATE MAP
      *          Default
      *         /       \
-     * Disabled     Enabled
-     *             /       \
-     * NotConnected      Connected
-     *                  /---------\
-     *               (all other states)
+     * Disabled      Enabled
+     *             /     \     \
+     * NotConnected  Verifying  Connected
+     *                         /---------\
+     *                       (all other states)
      */
     private WifiWatchdogStateMachine(Context context) {
         super(TAG);
         mContext = context;
         mContentResolver = context.getContentResolver();
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        mDnsPinger = new DnsPinger(mContext, "WifiWatchdogStateMachine.DnsPinger",
-                                this.getHandler().getLooper(), this.getHandler(),
-                                ConnectivityManager.TYPE_WIFI);
+        mWsmChannel.connectSync(mContext, getHandler(),
+                mWifiManager.getWifiStateMachineMessenger());
 
         setupNetworkReceiver();
 
@@ -221,16 +215,17 @@
             addState(mWatchdogDisabledState, mDefaultState);
             addState(mWatchdogEnabledState, mDefaultState);
                 addState(mNotConnectedState, mWatchdogEnabledState);
+                addState(mVerifyingLinkState, mWatchdogEnabledState);
                 addState(mConnectedState, mWatchdogEnabledState);
-                    addState(mDnsCheckingState, mConnectedState);
-                    addState(mDnsCheckFailureState, mConnectedState);
-                    addState(mDelayWalledGardenState, mConnectedState);
-                    addState(mWalledGardenState, mConnectedState);
-                    addState(mBlacklistedApState, mConnectedState);
+                    addState(mWalledGardenCheckState, mConnectedState);
                     addState(mOnlineWatchState, mConnectedState);
                     addState(mOnlineState, mConnectedState);
 
-        setInitialState(mWatchdogDisabledState);
+        if (isWatchdogEnabled()) {
+            setInitialState(mNotConnectedState);
+        } else {
+            setInitialState(mWatchdogDisabledState);
+        }
         updateSettings();
     }
 
@@ -242,19 +237,15 @@
         sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
 
         // Disable for wifi only devices.
-        if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null &&
-                sWifiOnly) {
+        if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null
+                && sWifiOnly) {
             putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, false);
         }
         WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
         wwsm.start();
-        wwsm.sendMessage(EVENT_WATCHDOG_TOGGLED);
         return wwsm;
     }
 
-    /**
-   *
-   */
     private void setupNetworkReceiver() {
         mBroadcastReceiver = new BroadcastReceiver() {
             @Override
@@ -263,10 +254,8 @@
                 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);
+                    obtainMessage(EVENT_RSSI_CHANGE,
+                            intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget();
                 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
                     sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,
                             intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
@@ -279,7 +268,7 @@
         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);
+        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
     }
 
     /**
@@ -311,39 +300,29 @@
 
         mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(
-                        Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS),
+                        Settings.Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS),
                         false, contentObserver);
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS),
-                false, contentObserver);
-        mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS),
                 false, contentObserver);
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS),
+                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_ARP_PINGS),
                 false, contentObserver);
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_DNS_PINGS),
+                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES),
                 false, contentObserver);
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES),
+                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS),
                 false, contentObserver);
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS),
+                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED),
                 false, contentObserver);
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(
-                        Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS),
-                        false, contentObserver);
-        mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED),
                 false, contentObserver);
         mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL),
                 false, contentObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP)
-                , false, contentObserver);
     }
 
     /**
@@ -375,17 +354,20 @@
         }
     }
 
-    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 [" + mConnectionInfo + "]");
-        pw.print("checkFailures   " + mNumCheckFailures);
-        pw.println(", bssids: " + mBssids);
-        pw.println("lastSingleCheck: " + mOnlineWatchState.lastCheckTime);
+        pw.print("State: " + getCurrentState());
+        pw.println("mWifiInfo: [" + mWifiInfo + "]");
+        pw.println("mLinkProperties: [" + mLinkProperties + "]");
+        pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]");
+        pw.println("mArpCheckIntervalMs: [" + mArpCheckIntervalMs+ "]");
+        pw.println("mWalledGardenIntervalMs: [" + mWalledGardenIntervalMs + "]");
+        pw.println("mNumArpPings: [" + mNumArpPings + "]");
+        pw.println("mMinArpResponses: [" + mMinArpResponses + "]");
+        pw.println("mArpPingTimeoutMs: [" + mArpPingTimeoutMs + "]");
+        pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]");
+        pw.println("mWalledGardenTestEnabled: [" + mWalledGardenTestEnabled + "]");
+        pw.println("mWalledGardenUrl: [" + mWalledGardenUrl + "]");
     }
 
     private boolean isWatchdogEnabled() {
@@ -393,31 +375,22 @@
     }
 
     private void updateSettings() {
-        mDnsCheckShortIntervalMs = Secure.getLong(mContentResolver,
-                Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS,
-                DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS);
-        mDnsCheckLongIntervalMs = Secure.getLong(mContentResolver,
-                Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS,
-                DEFAULT_DNS_CHECK_LONG_INTERVAL_MS);
-        mMaxSsidBlacklists = Secure.getInt(mContentResolver,
-                Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS,
-                DEFAULT_MAX_SSID_BLACKLISTS);
-        mNumDnsPings = Secure.getInt(mContentResolver,
-                Secure.WIFI_WATCHDOG_NUM_DNS_PINGS,
-                DEFAULT_NUM_DNS_PINGS);
-        mMinDnsResponses = Secure.getInt(mContentResolver,
-                Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES,
-                DEFAULT_MIN_DNS_RESPONSES);
-        mDnsPingTimeoutMs = Secure.getInt(mContentResolver,
-                Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS,
-                DEFAULT_DNS_PING_TIMEOUT_MS);
-        mBlacklistFollowupIntervalMs = Secure.getLong(mContentResolver,
-                Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS,
-                DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS);
-        //TODO: enable this by default after changing watchdog behavior
-        //Also, update settings description
+        if (DBG) log("Updating secure settings");
+
+        mArpCheckIntervalMs = Secure.getLong(mContentResolver,
+                Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS,
+                DEFAULT_ARP_CHECK_INTERVAL_MS);
+        mNumArpPings = Secure.getInt(mContentResolver,
+                Secure.WIFI_WATCHDOG_NUM_ARP_PINGS,
+                DEFAULT_NUM_ARP_PINGS);
+        mMinArpResponses = Secure.getInt(mContentResolver,
+                Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES,
+                DEFAULT_MIN_ARP_RESPONSES);
+        mArpPingTimeoutMs = Secure.getInt(mContentResolver,
+                Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS,
+                DEFAULT_ARP_PING_TIMEOUT_MS);
         mPoorNetworkDetectionEnabled = getSettingsBoolean(mContentResolver,
-                Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, false);
+                Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, true);
         mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver,
                 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true);
         mWalledGardenUrl = getSettingsStr(mContentResolver,
@@ -426,69 +399,6 @@
         mWalledGardenIntervalMs = Secure.getLong(mContentResolver,
                 Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS,
                 DEFAULT_WALLED_GARDEN_INTERVAL_MS);
-        mShowDisabledNotification = getSettingsBoolean(mContentResolver,
-                Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP, true);
-    }
-
-    /**
-     * 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() + ")";
-    }
-
-    /**
-     * Uses {@link #mConnectionInfo}.
-     */
-    private void updateBssids() {
-        String curSsid = mConnectionInfo.getSSID();
-        List<ScanResult> results = mWifiManager.getScanResults();
-        int oldNumBssids = mBssids.size();
-
-        if (results == null) {
-            if (DBG) {
-                log("updateBssids: Got null scan results!");
-            }
-            return;
-        }
-
-        for (ScanResult result : results) {
-            if (result == null || result.SSID == null) {
-                if (DBG) {
-                    log("Received invalid scan result: " + result);
-                }
-                continue;
-            }
-            if (curSsid.equals(result.SSID))
-                mBssids.add(result.BSSID);
-        }
-    }
-
-    private void resetWatchdogState() {
-        if (DBG) {
-            log("Resetting watchdog state...");
-        }
-        mConnectionInfo = null;
-        mDisableAPNextFailure = false;
-        mLastWalledGardenCheckTime = null;
-        mNumCheckFailures = 0;
-        mBssids.clear();
-        setDisabledNetworkNotificationVisible(false);
-        setWalledGardenNotificationVisible(false);
     }
 
     private void setWalledGardenNotificationVisible(boolean visible) {
@@ -507,7 +417,7 @@
 
             CharSequence title = r.getString(R.string.wifi_available_sign_in, 0);
             CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed,
-                    mConnectionInfo.getSSID());
+                    mWifiInfo.getSSID());
 
             Notification notification = new Notification();
             notification.when = 0;
@@ -524,41 +434,6 @@
         mWalledGardenNotificationShown = visible;
     }
 
-    private void setDisabledNetworkNotificationVisible(boolean visible) {
-        // If it should be hidden and it is already hidden, then noop
-        if (!visible && !mDisabledNotificationShown) {
-            return;
-        }
-
-        Resources r = Resources.getSystem();
-        NotificationManager notificationManager = (NotificationManager) mContext
-            .getSystemService(Context.NOTIFICATION_SERVICE);
-
-        if (visible) {
-            CharSequence title = r.getText(R.string.wifi_watchdog_network_disabled);
-            String msg = mConnectionInfo.getSSID() +
-                r.getText(R.string.wifi_watchdog_network_disabled_detailed);
-
-            Notification wifiDisabledWarning = new Notification.Builder(mContext)
-                .setSmallIcon(R.drawable.stat_sys_warning)
-                .setDefaults(Notification.DEFAULT_ALL)
-                .setTicker(title)
-                .setContentTitle(title)
-                .setContentText(msg)
-                .setContentIntent(PendingIntent.getActivity(mContext, 0,
-                            new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK)
-                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0))
-                .setWhen(System.currentTimeMillis())
-                .setAutoCancel(true)
-                .getNotification();
-
-            notificationManager.notify(DISABLED_NETWORK_NOTIFICATION_ID, 1, wifiDisabledWarning);
-        } else {
-            notificationManager.cancel(DISABLED_NETWORK_NOTIFICATION_ID, 1);
-        }
-        mDisabledNotificationShown = visible;
-    }
-
     class DefaultState extends State {
         @Override
         public boolean processMessage(Message msg) {
@@ -568,11 +443,20 @@
                     if (DBG) {
                         log("Updating wifi-watchdog secure settings");
                     }
-                    return HANDLED;
-            }
-            if (DBG) {
-                log("Caught message " + msg.what + " in state " +
-                        getCurrentState().getName());
+                    break;
+                case EVENT_RSSI_CHANGE:
+                    mCurrentSignalLevel = WifiManager.calculateSignalLevel(msg.arg1,
+                            WifiManager.RSSI_LEVELS);
+                    break;
+                case EVENT_WIFI_RADIO_STATE_CHANGE:
+                case EVENT_NETWORK_STATE_CHANGE:
+                case CMD_ARP_CHECK:
+                case CMD_DELAYED_WALLED_GARDEN_CHECK:
+                    //ignore
+                    break;
+                default:
+                    log("Unhandled message " + msg + " in state " + getCurrentState().getName());
+                    break;
             }
             return HANDLED;
         }
@@ -586,6 +470,20 @@
                     if (isWatchdogEnabled())
                         transitionTo(mNotConnectedState);
                     return HANDLED;
+                case EVENT_NETWORK_STATE_CHANGE:
+                    Intent intent = (Intent) msg.obj;
+                    NetworkInfo networkInfo = (NetworkInfo)
+                            intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+
+                    switch (networkInfo.getDetailedState()) {
+                        case VERIFYING_POOR_LINK:
+                            if (DBG) log("Watchdog disabled, verify link");
+                            mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
             }
             return NOT_HANDLED;
         }
@@ -594,10 +492,8 @@
     class WatchdogEnabledState extends State {
         @Override
         public void enter() {
-            resetWatchdogState();
-            mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
             if (DBG) log("WifiWatchdogService enabled");
-        }
+       }
 
         @Override
         public boolean processMessage(Message msg) {
@@ -605,77 +501,57 @@
                 case EVENT_WATCHDOG_TOGGLED:
                     if (!isWatchdogEnabled())
                         transitionTo(mWatchdogDisabledState);
-                    return HANDLED;
+                    break;
                 case EVENT_NETWORK_STATE_CHANGE:
-                    Intent stateChangeIntent = (Intent) msg.obj;
+                    Intent intent = (Intent) msg.obj;
                     NetworkInfo networkInfo = (NetworkInfo)
-                            stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+                            intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
 
-                    setDisabledNetworkNotificationVisible(false);
-                    setWalledGardenNotificationVisible(false);
-                    switch (networkInfo.getState()) {
-                        case CONNECTED:
-                            WifiInfo wifiInfo = (WifiInfo)
-                                stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
-                            if (wifiInfo == null) {
-                                loge("Connected --> WifiInfo object null!");
-                                return HANDLED;
-                            }
-
-                            if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
-                                loge("Received wifiInfo object with null elts: "
-                                        + wifiInfoToStr(wifiInfo));
-                                return HANDLED;
-                            }
-
-                            initConnection(wifiInfo);
-                            mConnectionInfo = wifiInfo;
-                            mNetEventCounter++;
+                    switch (networkInfo.getDetailedState()) {
+                        case VERIFYING_POOR_LINK:
+                            mLinkProperties = (LinkProperties) intent.getParcelableExtra(
+                                    WifiManager.EXTRA_LINK_PROPERTIES);
+                            mWifiInfo = (WifiInfo) intent.getParcelableExtra(
+                                    WifiManager.EXTRA_WIFI_INFO);
                             if (mPoorNetworkDetectionEnabled) {
-                                updateBssids();
-                                transitionTo(mDnsCheckingState);
+                                if (mWifiInfo == null) {
+                                    log("Ignoring link verification, mWifiInfo is NULL");
+                                    mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
+                                } else {
+                                    transitionTo(mVerifyingLinkState);
+                                }
                             } else {
-                                transitionTo(mDelayWalledGardenState);
+                                mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
+                            }
+                            break;
+                        case CONNECTED:
+                            if (shouldCheckWalledGarden()) {
+                                transitionTo(mWalledGardenCheckState);
+                            } else {
+                                transitionTo(mOnlineWatchState);
                             }
                             break;
                         default:
-                            mNetEventCounter++;
                             transitionTo(mNotConnectedState);
                             break;
                     }
-                    return HANDLED;
+                    break;
                 case EVENT_WIFI_RADIO_STATE_CHANGE:
                     if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
                         if (DBG) log("WifiStateDisabling -- Resetting WatchdogState");
-                        resetWatchdogState();
-                        mNetEventCounter++;
                         transitionTo(mNotConnectedState);
                     }
-                    return HANDLED;
+                    break;
+                default:
+                    return NOT_HANDLED;
             }
 
-            return NOT_HANDLED;
-        }
-
-        /**
-         * @param wifiInfo Info object with non-null ssid and bssid
-         */
-        private void initConnection(WifiInfo wifiInfo) {
-            if (DBG) {
-                log("Connected:: old " + wifiInfoToStr(mConnectionInfo) +
-                        " ==> new " + wifiInfoToStr(wifiInfo));
-            }
-
-            if (mConnectionInfo == null || !wifiInfo.getSSID().equals(mConnectionInfo.getSSID())) {
-                resetWatchdogState();
-            } else if (!wifiInfo.getBSSID().equals(mConnectionInfo.getBSSID())) {
-                mDisableAPNextFailure = false;
-            }
+            setWalledGardenNotificationVisible(false);
+            return HANDLED;
         }
 
         @Override
         public void exit() {
-            mContext.unregisterReceiver(mBroadcastReceiver);
             if (DBG) log("WifiWatchdogService disabled");
         }
     }
@@ -683,17 +559,74 @@
     class NotConnectedState extends State {
     }
 
-    class ConnectedState extends State {
+    class VerifyingLinkState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log(getName() + "\n");
+            //Treat entry as an rssi change
+            handleRssiChange();
+        }
+
+        private void handleRssiChange() {
+            if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) {
+                //stay here
+                if (DBG) log("enter VerifyingLinkState, stay level: " + mCurrentSignalLevel);
+            } else {
+                if (DBG) log("enter VerifyingLinkState, arp check level: " + mCurrentSignalLevel);
+                sendMessage(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0));
+            }
+        }
+
         @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
-                case EVENT_SCAN_RESULTS_AVAILABLE:
-                    if (mPoorNetworkDetectionEnabled) {
-                        updateBssids();
-                    }
-                    return HANDLED;
                 case EVENT_WATCHDOG_SETTINGS_CHANGE:
                     updateSettings();
+                    if (!mPoorNetworkDetectionEnabled) {
+                        mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
+                    }
+                    break;
+                case EVENT_RSSI_CHANGE:
+                    int signalLevel = WifiManager.calculateSignalLevel(msg.arg1,
+                            WifiManager.RSSI_LEVELS);
+                    if (DBG) log("RSSI change old: " + mCurrentSignalLevel + "new: " + signalLevel);
+                    mCurrentSignalLevel = signalLevel;
+
+                    handleRssiChange();
+                    break;
+                case CMD_ARP_CHECK:
+                    if (msg.arg1 == mArpToken) {
+                        if (doArpTest(FULL_ARP_CHECK) == true) {
+                            if (DBG) log("Notify link is good " + mCurrentSignalLevel);
+                            mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
+                        } else {
+                            if (DBG) log("Continue ARP check, rssi level: " + mCurrentSignalLevel);
+                            sendMessageDelayed(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0),
+                                    mArpCheckIntervalMs);
+                        }
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    class ConnectedState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log(getName() + "\n");
+        }
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_WATCHDOG_SETTINGS_CHANGE:
+                    updateSettings();
+                    //STOPSHIP: Remove this at ship
+                    DBG = true;
+                    if (DBG) log("Updated secure settings and turned debug on");
+
                     if (mPoorNetworkDetectionEnabled) {
                         transitionTo(mOnlineWatchState);
                     } else {
@@ -705,402 +638,162 @@
         }
     }
 
-    class DnsCheckingState extends State {
-        List<InetAddress> mDnsList;
-        int[] dnsCheckSuccesses;
-        String dnsCheckLogStr;
-        String[] dnsResponseStrs;
-        /** Keeps track of active dns pings.  Map is from pingID to index in mDnsList */
-        HashMap<Integer, Integer> idDnsMap = new HashMap<Integer, Integer>();
-
+    class WalledGardenCheckState extends State {
+        private int mWalledGardenToken = 0;
         @Override
         public void enter() {
-            mDnsList = mDnsPinger.getDnsList();
-            int numDnses = mDnsList.size();
-            dnsCheckSuccesses = new int[numDnses];
-            dnsResponseStrs = new String[numDnses];
-            for (int i = 0; i < numDnses; i++)
-                dnsResponseStrs[i] = "";
-
-            if (DBG) {
-                dnsCheckLogStr = String.format("Pinging %s on ssid [%s]: ",
-                        mDnsList, mConnectionInfo.getSSID());
-                log(dnsCheckLogStr);
-            }
-
-            idDnsMap.clear();
-            for (int i=0; i < mNumDnsPings; i++) {
-                for (int j = 0; j < numDnses; j++) {
-                    idDnsMap.put(mDnsPinger.pingDnsAsync(mDnsList.get(j), mDnsPingTimeoutMs,
-                            DNS_START_DELAY_MS + DNS_INTRATEST_PING_INTERVAL_MS * i), j);
-                }
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            if (msg.what != DnsPinger.DNS_PING_RESULT) {
-                return NOT_HANDLED;
-            }
-
-            int pingID = msg.arg1;
-            int pingResponseTime = msg.arg2;
-
-            Integer dnsServerId = idDnsMap.get(pingID);
-            if (dnsServerId == null) {
-                loge("Received a Dns response with unknown ID!");
-                return HANDLED;
-            }
-
-            idDnsMap.remove(pingID);
-            if (pingResponseTime >= 0)
-                dnsCheckSuccesses[dnsServerId]++;
-
-            if (DBG) {
-                if (pingResponseTime >= 0) {
-                    dnsResponseStrs[dnsServerId] += "|" + pingResponseTime;
-                } else {
-                    dnsResponseStrs[dnsServerId] += "|x";
-                }
-            }
-
-            /**
-             * After a full ping count, if we have more responses than this
-             * cutoff, the outcome is success; else it is 'failure'.
-             */
-
-            /**
-             * Our final success count will be at least this big, so we're
-             * guaranteed to succeed.
-             */
-            if (dnsCheckSuccesses[dnsServerId] >= mMinDnsResponses) {
-                // DNS CHECKS OK, NOW WALLED GARDEN
-                if (DBG) {
-                    log(makeLogString() + "  SUCCESS");
-                }
-
-                if (!shouldCheckWalledGarden()) {
-                    transitionTo(mOnlineWatchState);
-                    return HANDLED;
-                }
-
-                transitionTo(mDelayWalledGardenState);
-                return HANDLED;
-            }
-
-            if (idDnsMap.isEmpty()) {
-                if (DBG) {
-                    log(makeLogString() + "  FAILURE");
-                }
-                transitionTo(mDnsCheckFailureState);
-                return HANDLED;
-            }
-
-            return HANDLED;
-        }
-
-        private String makeLogString() {
-            String logStr = dnsCheckLogStr;
-            for (String respStr : dnsResponseStrs)
-                logStr += " [" + respStr + "]";
-            return logStr;
-        }
-
-        @Override
-        public void exit() {
-            mDnsPinger.cancelPings();
-        }
-
-        private boolean shouldCheckWalledGarden() {
-            if (!mWalledGardenTestEnabled) {
-                if (DBG)
-                    log("Skipping walled garden check - disabled");
-                return false;
-            }
-            long waitTime = waitTime(mWalledGardenIntervalMs,
-                    mLastWalledGardenCheckTime);
-            if (waitTime > 0) {
-                if (DBG) {
-                    log("Skipping walled garden check - wait " +
-                            waitTime + " ms.");
-                }
-                return false;
-            }
-            return true;
-        }
-    }
-
-    class DelayWalledGardenState extends State {
-        @Override
-        public void enter() {
-            sendMessageDelayed(MESSAGE_DELAYED_WALLED_GARDEN_CHECK, WALLED_GARDEN_START_DELAY_MS);
+            if (DBG) log(getName() + "\n");
+            sendMessageDelayed(obtainMessage(CMD_DELAYED_WALLED_GARDEN_CHECK,
+                    ++mWalledGardenToken, 0), WALLED_GARDEN_START_DELAY_MS);
         }
 
         @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
-                case MESSAGE_DELAYED_WALLED_GARDEN_CHECK:
-                    mLastWalledGardenCheckTime = SystemClock.elapsedRealtime();
-                    if (isWalledGardenConnection()) {
-                        if (DBG) log("Walled garden test complete - walled garden detected");
-                        transitionTo(mWalledGardenState);
-                    } else {
-                        if (DBG) log("Walled garden test complete - online");
-                        if (mPoorNetworkDetectionEnabled) {
-                            transitionTo(mOnlineWatchState);
-                        } else {
-                            transitionTo(mOnlineState);
+                case CMD_DELAYED_WALLED_GARDEN_CHECK:
+                    if (msg.arg1 == mWalledGardenToken) {
+                        mLastWalledGardenCheckTime = SystemClock.elapsedRealtime();
+                        if (isWalledGardenConnection()) {
+                            if (DBG) log("Walled garden detected");
+                            setWalledGardenNotificationVisible(true);
                         }
+                        transitionTo(mOnlineWatchState);
                     }
-                    return HANDLED;
+                    break;
                 default:
                     return NOT_HANDLED;
             }
+            return HANDLED;
         }
     }
 
     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;
-
-        /** Keeps track of dns pings.  Map is from pingID to InetAddress used for ping */
-        HashMap<Integer, InetAddress> pingInfoMap = new HashMap<Integer, InetAddress>();
-
-        @Override
         public void enter() {
-            lastCheckTime = SystemClock.elapsedRealtime();
-            signalUnstable = false;
-            checkGuard++;
-            unstableSignalChecks = false;
-            pingInfoMap.clear();
-            triggerSingleDnsCheck();
+            if (DBG) log(getName() + "\n");
+            if (mPoorNetworkDetectionEnabled) {
+                //Treat entry as an rssi change
+                handleRssiChange();
+            } else {
+                transitionTo(mOnlineState);
+            }
+        }
+
+        private void handleRssiChange() {
+            if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) {
+                if (DBG) log("Transition out, below cut off level: " + mCurrentSignalLevel);
+                mWsmChannel.sendMessage(POOR_LINK_DETECTED);
+            } else if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) {
+                if (DBG) log("Start monitoring, level: " + mCurrentSignalLevel);
+                sendMessage(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0));
+            }
         }
 
         @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 case EVENT_RSSI_CHANGE:
-                    if (msg.arg1 != mNetEventCounter) {
-                        if (DBG) {
-                            log("Rssi change message out of sync, ignoring");
-                        }
-                        return HANDLED;
-                    }
-                    int newRssi = msg.arg2;
-                    signalUnstable = !rssiStrengthAboveCutoff(newRssi);
-                    if (DBG) {
-                        log("OnlineWatchState:: new rssi " + newRssi + " --> level " +
-                                WifiManager.calculateSignalLevel(newRssi, WIFI_SIGNAL_LEVELS));
-                    }
+                    int signalLevel = WifiManager.calculateSignalLevel(msg.arg1,
+                            WifiManager.RSSI_LEVELS);
+                    if (DBG) log("RSSI change old: " + mCurrentSignalLevel + "new: " + signalLevel);
+                    mCurrentSignalLevel = signalLevel;
 
-                    if (signalUnstable && !unstableSignalChecks) {
-                        if (DBG) {
-                            log("Sending triggered check msg");
-                        }
-                        triggerSingleDnsCheck();
-                    }
-                    return HANDLED;
-                case MESSAGE_SINGLE_DNS_CHECK:
-                    if (msg.arg1 != checkGuard) {
-                        if (DBG) {
-                            log("Single check msg out of sync, ignoring.");
-                        }
-                        return HANDLED;
-                    }
-                    lastCheckTime = SystemClock.elapsedRealtime();
-                    pingInfoMap.clear();
-                    for (InetAddress curDns: mDnsPinger.getDnsList()) {
-                        pingInfoMap.put(mDnsPinger.pingDnsAsync(curDns, mDnsPingTimeoutMs, 0),
-                                curDns);
-                    }
-                    return HANDLED;
-                case DnsPinger.DNS_PING_RESULT:
-                    InetAddress curDnsServer = pingInfoMap.get(msg.arg1);
-                    if (curDnsServer == null) {
-                        return HANDLED;
-                    }
-                    pingInfoMap.remove(msg.arg1);
-                    int responseTime = msg.arg2;
-                    if (responseTime >= 0) {
-                        if (DBG) {
-                            log("Single DNS ping OK. Response time: "
-                                    + responseTime + " from DNS " + curDnsServer);
-                        }
-                        pingInfoMap.clear();
+                    handleRssiChange();
 
-                        checkGuard++;
-                        unstableSignalChecks = false;
-                        triggerSingleDnsCheck();
-                    } else {
-                        if (pingInfoMap.isEmpty()) {
-                            if (DBG) {
-                                log("Single dns ping failure. All dns servers failed, "
-                                        + "starting full checks.");
+                    break;
+                case CMD_ARP_CHECK:
+                    if (msg.arg1 == mArpToken) {
+                        if (doArpTest(SINGLE_ARP_CHECK) != true) {
+                            if (DBG) log("single ARP fail, full ARP check");
+                            //do a full test
+                            if (doArpTest(FULL_ARP_CHECK) != true) {
+                                if (DBG) log("notify full ARP fail, level: " + mCurrentSignalLevel);
+                                mWsmChannel.sendMessage(POOR_LINK_DETECTED);
                             }
-                            transitionTo(mDnsCheckingState);
+                        }
+
+                        if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) {
+                            if (DBG) log("Continue ARP check, rssi level: " + mCurrentSignalLevel);
+                            sendMessageDelayed(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0),
+                                    mArpCheckIntervalMs);
                         }
                     }
-                    return HANDLED;
+                    break;
+                default:
+                    return NOT_HANDLED;
             }
-            return NOT_HANDLED;
-        }
-
-        @Override
-        public void exit() {
-            mDnsPinger.cancelPings();
-        }
-
-        /**
-         * Times a dns check with an interval based on {@link #signalUnstable}
-         */
-        private void triggerSingleDnsCheck() {
-            long waitInterval;
-            if (signalUnstable) {
-                waitInterval = mDnsCheckShortIntervalMs;
-                unstableSignalChecks = true;
-            } else {
-                waitInterval = mDnsCheckLongIntervalMs;
-            }
-            sendMessageDelayed(obtainMessage(MESSAGE_SINGLE_DNS_CHECK, checkGuard, 0),
-                    waitTime(waitInterval, lastCheckTime));
+            return HANDLED;
         }
     }
 
-
     /* Child state of ConnectedState indicating that we are online
      * and there is nothing to do
      */
     class OnlineState extends State {
     }
 
-    class DnsCheckFailureState extends State {
-
-        @Override
-        public void enter() {
-            mNumCheckFailures++;
-            obtainMessage(MESSAGE_HANDLE_BAD_AP, mNetEventCounter, 0).sendToTarget();
+    private boolean shouldCheckWalledGarden() {
+        if (!mWalledGardenTestEnabled) {
+            if (DBG) log("Skipping walled garden check - disabled");
+            return false;
         }
 
-        @Override
-        public boolean processMessage(Message msg) {
-            if (msg.what != MESSAGE_HANDLE_BAD_AP) {
-                return NOT_HANDLED;
+        long waitTime = (mWalledGardenIntervalMs + mLastWalledGardenCheckTime)
+            - SystemClock.elapsedRealtime();
+
+        if (mLastWalledGardenCheckTime != 0 && waitTime > 0) {
+            if (DBG) {
+                log("Skipping walled garden check - wait " +
+                        waitTime + " ms.");
             }
+            return false;
+        }
+        return true;
+    }
 
-            if (msg.arg1 != mNetEventCounter) {
-                if (DBG) {
-                    log("Msg out of sync, ignoring...");
-                }
-                return HANDLED;
-            }
+    private boolean doArpTest(int type) {
+        boolean success;
 
-            if (mDisableAPNextFailure || mNumCheckFailures >= mBssids.size()
-                    || mNumCheckFailures >= mMaxSsidBlacklists) {
-                if (sWifiOnly) {
-                    log("Would disable bad network, but device has no mobile data!" +
-                            "  Going idle...");
-                    // This state should be called idle -- will be changing flow.
-                    transitionTo(mNotConnectedState);
-                    return HANDLED;
-                }
+        String iface = mLinkProperties.getInterfaceName();
+        String mac = mWifiInfo.getMacAddress();
+        InetAddress inetAddress = null;
+        InetAddress gateway = null;
 
-                // TODO : Unban networks if they had low signal ?
-                log("Disabling current SSID " + wifiInfoToStr(mConnectionInfo)
-                        + ".  " + "numCheckFailures " + mNumCheckFailures
-                        + ", numAPs " + mBssids.size());
-                int networkId = mConnectionInfo.getNetworkId();
-                if (!mHasConnectedWifiManager) {
-                    mWifiManager.asyncConnect(mContext, getHandler());
-                    mHasConnectedWifiManager = true;
-                }
-                mWifiManager.disableNetwork(networkId, WifiConfiguration.DISABLED_DNS_FAILURE);
-                if (mShowDisabledNotification) {
-                    setDisabledNetworkNotificationVisible(true);
-                }
-                transitionTo(mNotConnectedState);
+        for (LinkAddress la : mLinkProperties.getLinkAddresses()) {
+            inetAddress = la.getAddress();
+            break;
+        }
+
+        for (RouteInfo route : mLinkProperties.getRoutes()) {
+            gateway = route.getGateway();
+            break;
+        }
+
+        if (DBG) log("ARP " + iface + "addr: " + inetAddress + "mac: " + mac + "gw: " + gateway);
+
+        try {
+            ArpPeer peer = new ArpPeer(iface, inetAddress, mac, gateway);
+            if (type == SINGLE_ARP_CHECK) {
+                success = (peer.doArp(mArpPingTimeoutMs) != null);
+                if (DBG) log("single ARP test result: " + success);
             } else {
-                log("Blacklisting current BSSID.  " + wifiInfoToStr(mConnectionInfo)
-                       + "numCheckFailures " + mNumCheckFailures + ", numAPs " + mBssids.size());
-
-                mWifiManager.addToBlacklist(mConnectionInfo.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 (DBG) {
-                    log("WalledGardenState::Msg out of sync, ignoring...");
+                int responses = 0;
+                for (int i=0; i < mNumArpPings; i++) {
+                    if(peer.doArp(mArpPingTimeoutMs) != null) responses++;
                 }
-                return HANDLED;
+                if (DBG) log("full ARP test result: " + responses + "/" + mNumArpPings);
+                success = (responses >= mMinArpResponses);
             }
-            setWalledGardenNotificationVisible(true);
-            if (mPoorNetworkDetectionEnabled) {
-                transitionTo(mOnlineWatchState);
-            } else {
-                transitionTo(mOnlineState);
-            }
-            return HANDLED;
+            peer.close();
+        } catch (SocketException se) {
+            //Consider an Arp socket creation issue as a successful Arp
+            //test to avoid any wifi connectivity issues
+            loge("ARP test initiation failure: " + se);
+            success = true;
         }
+
+        return success;
     }
 
-    class BlacklistedApState extends State {
-        @Override
-        public void enter() {
-            mDisableAPNextFailure = true;
-            sendMessageDelayed(obtainMessage(MESSAGE_NETWORK_FOLLOWUP, mNetEventCounter, 0),
-                    mBlacklistFollowupIntervalMs);
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            if (msg.what != MESSAGE_NETWORK_FOLLOWUP) {
-                return NOT_HANDLED;
-            }
-
-            if (msg.arg1 != mNetEventCounter) {
-                if (DBG) {
-                    log("BlacklistedApState::Msg out of sync, ignoring...");
-                }
-                return HANDLED;
-            }
-
-            transitionTo(mDnsCheckingState);
-            return HANDLED;
-        }
-    }
-
-
     /**
      * Convenience function for retrieving a single secure settings value
      * as a string with a default value.