Merge "Use android.os.Process.THREAD_PRIORITY_* symbols"
diff --git a/Android.mk b/Android.mk
index c838600..e987f91 100644
--- a/Android.mk
+++ b/Android.mk
@@ -110,7 +110,9 @@
 	core/java/android/net/IConnectivityManager.aidl \
 	core/java/android/net/INetworkManagementEventObserver.aidl \
 	core/java/android/net/IThrottleManager.aidl \
+	core/java/android/net/INetworkPolicyListener.aidl \
 	core/java/android/net/INetworkPolicyManager.aidl \
+	core/java/android/net/INetworkStatsService.aidl \
 	core/java/android/nfc/ILlcpConnectionlessSocket.aidl \
 	core/java/android/nfc/ILlcpServiceSocket.aidl \
 	core/java/android/nfc/ILlcpSocket.aidl \
@@ -123,7 +125,6 @@
 	core/java/android/os/IHardwareService.aidl \
 	core/java/android/os/IMessenger.aidl \
 	core/java/android/os/INetworkManagementService.aidl \
-	core/java/android/os/INetStatService.aidl \
 	core/java/android/os/IPermissionController.aidl \
 	core/java/android/os/IPowerManager.aidl \
     core/java/android/os/IRemoteCallback.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index b19bed0..1e59ff6 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -98,6 +98,7 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/nfc)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libstagefright_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os)
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/api/current.txt b/api/current.txt
index 9e87e9c..3a90ef7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6814,6 +6814,7 @@
     method public void setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory);
     method public void setDistinct(boolean);
     method public void setProjectionMap(java.util.Map<java.lang.String, java.lang.String>);
+    method public void setStrict(boolean);
     method public void setTables(java.lang.String);
   }
 
@@ -11041,6 +11042,7 @@
     method public static android.net.NetworkInfo.DetailedState valueOf(java.lang.String);
     method public static final android.net.NetworkInfo.DetailedState[] values();
     enum_constant public static final android.net.NetworkInfo.DetailedState AUTHENTICATING;
+    enum_constant public static final android.net.NetworkInfo.DetailedState BLOCKED;
     enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTED;
     enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTING;
     enum_constant public static final android.net.NetworkInfo.DetailedState DISCONNECTED;
@@ -20970,6 +20972,8 @@
   }
 
   public abstract interface MenuItem {
+    method public abstract boolean collapseActionView();
+    method public abstract boolean expandActionView();
     method public abstract android.view.View getActionView();
     method public abstract char getAlphabeticShortcut();
     method public abstract int getGroupId();
@@ -20983,6 +20987,7 @@
     method public abstract java.lang.CharSequence getTitle();
     method public abstract java.lang.CharSequence getTitleCondensed();
     method public abstract boolean hasSubMenu();
+    method public abstract boolean isActionViewExpanded();
     method public abstract boolean isCheckable();
     method public abstract boolean isChecked();
     method public abstract boolean isEnabled();
@@ -20997,19 +21002,27 @@
     method public abstract android.view.MenuItem setIcon(int);
     method public abstract android.view.MenuItem setIntent(android.content.Intent);
     method public abstract android.view.MenuItem setNumericShortcut(char);
+    method public abstract android.view.MenuItem setOnActionExpandListener(android.view.MenuItem.OnActionExpandListener);
     method public abstract android.view.MenuItem setOnMenuItemClickListener(android.view.MenuItem.OnMenuItemClickListener);
     method public abstract android.view.MenuItem setShortcut(char, char);
     method public abstract void setShowAsAction(int);
+    method public abstract android.view.MenuItem setShowAsActionFlags(int);
     method public abstract android.view.MenuItem setTitle(java.lang.CharSequence);
     method public abstract android.view.MenuItem setTitle(int);
     method public abstract android.view.MenuItem setTitleCondensed(java.lang.CharSequence);
     method public abstract android.view.MenuItem setVisible(boolean);
     field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
+    field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
     field public static final int SHOW_AS_ACTION_IF_ROOM = 1; // 0x1
     field public static final int SHOW_AS_ACTION_NEVER = 0; // 0x0
     field public static final int SHOW_AS_ACTION_WITH_TEXT = 4; // 0x4
   }
 
+  public static abstract interface MenuItem.OnActionExpandListener {
+    method public abstract boolean onMenuItemActionCollapse(android.view.MenuItem);
+    method public abstract boolean onMenuItemActionExpand(android.view.MenuItem);
+  }
+
   public static abstract interface MenuItem.OnMenuItemClickListener {
     method public abstract boolean onMenuItemClick(android.view.MenuItem);
   }
@@ -23177,6 +23190,7 @@
   public final class InputMethodSubtype implements android.os.Parcelable {
     method public boolean containsExtraValueKey(java.lang.String);
     method public int describeContents();
+    method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
     method public java.lang.String getExtraValue();
     method public java.lang.String getExtraValueOf(java.lang.String);
     method public int getIconResId();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a660bd7..aecec66 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1545,6 +1545,8 @@
     public static final String NETWORKMANAGEMENT_SERVICE = "network_management";
 
     /** {@hide} */
+    public static final String NETWORK_STATS_SERVICE = "netstats";
+    /** {@hide} */
     public static final String NETWORK_POLICY_SERVICE = "netpolicy";
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index b6aca2b..9c09e81 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -24,8 +24,8 @@
 
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.regex.Pattern;
 
 /**
@@ -43,7 +43,7 @@
     private StringBuilder mWhereClause = null;  // lazily created
     private boolean mDistinct;
     private SQLiteDatabase.CursorFactory mFactory;
-    private boolean mStrictProjectionMap;
+    private boolean mStrict;
 
     public SQLiteQueryBuilder() {
         mDistinct = false;
@@ -145,10 +145,37 @@
     }
 
     /**
+     * Need to keep this to not break the build until ContactsProvider2 has been changed to
+     * use the new API
+     * TODO: Remove this
      * @hide
      */
     public void setStrictProjectionMap(boolean flag) {
-        mStrictProjectionMap = flag;
+    }
+
+    /**
+     * When set, the selection is verified against malicious arguments.
+     * When using this class to create a statement using
+     * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)},
+     * non-numeric limits will raise an exception. If a projection map is specified, fields
+     * not in that map will be ignored.
+     * If this class is used to execute the statement directly using
+     * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)}
+     * or
+     * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)},
+     * additionally also parenthesis escaping selection are caught.
+     *
+     * To summarize: To get maximum protection against malicious third party apps (for example
+     * content provider consumers), make sure to do the following:
+     * <ul>
+     * <li>Set this value to true</li>
+     * <li>Use a projection map</li>
+     * <li>Use one of the query overloads instead of getting the statement as a sql string</li>
+     * </ul>
+     * By default, this value is false.
+     */
+    public void setStrict(boolean flag) {
+        mStrict = flag;
     }
 
     /**
@@ -217,13 +244,6 @@
         }
     }
 
-    private static void appendClauseEscapeClause(StringBuilder s, String name, String clause) {
-        if (!TextUtils.isEmpty(clause)) {
-            s.append(name);
-            DatabaseUtils.appendEscapedSQLString(s, clause);
-        }
-    }
-
     /**
      * Add the names that are non-null in columns to s, separating
      * them with commas.
@@ -320,6 +340,19 @@
             return null;
         }
 
+        if (mStrict && selection != null && selection.length() > 0) {
+            // Validate the user-supplied selection to detect syntactic anomalies
+            // in the selection string that could indicate a SQL injection attempt.
+            // The idea is to ensure that the selection clause is a valid SQL expression
+            // by compiling it twice: once wrapped in parentheses and once as
+            // originally specified. An attacker cannot create an expression that
+            // would escape the SQL expression while maintaining balanced parentheses
+            // in both the wrapped and original forms.
+            String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
+                    having, sortOrder, limit);
+            validateSql(db, sqlForValidation); // will throw if query is invalid
+        }
+
         String sql = buildQuery(
                 projectionIn, selection, groupBy, having,
                 sortOrder, limit);
@@ -329,7 +362,20 @@
         }
         return db.rawQueryWithFactory(
                 mFactory, sql, selectionArgs,
-                SQLiteDatabase.findEditTable(mTables));
+                SQLiteDatabase.findEditTable(mTables)); // will throw if query is invalid
+    }
+
+    /**
+     * Verifies that a SQL statement is valid by compiling it.
+     * If the SQL statement is not valid, this method will throw a {@link SQLiteException}.
+     */
+    private void validateSql(SQLiteDatabase db, String sql) {
+        db.lock(sql);
+        try {
+            new SQLiteCompiledSql(db, sql).releaseSqlStatement();
+        } finally {
+            db.unlock();
+        }
     }
 
     /**
@@ -541,7 +587,7 @@
                         continue;
                     }
 
-                    if (!mStrictProjectionMap &&
+                    if (!mStrict &&
                             ( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
                         /* A column alias already exist */
                         projection[i] = userColumn;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 419288b..c72c4b0 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -22,7 +22,6 @@
 import android.os.RemoteException;
 
 import java.net.InetAddress;
-import java.net.UnknownHostException;
 
 /**
  * Class that answers queries about the state of network connectivity. It also
@@ -40,8 +39,9 @@
  * state of the available networks</li>
  * </ol>
  */
-public class ConnectivityManager
-{
+public class ConnectivityManager {
+    private static final String TAG = "ConnectivityManager";
+
     /**
      * A change in network connectivity has occurred. A connection has either
      * been established or lost. The NetworkInfo for the affected network is
@@ -109,7 +109,7 @@
      * The lookup key for an int that provides information about
      * our connection to the internet at large.  0 indicates no connection,
      * 100 indicates a great connection.  Retrieve it with
-     * {@link android.content.Intent@getIntExtra(String)}.
+     * {@link android.content.Intent#getIntExtra(String, int)}.
      * {@hide}
      */
     public static final String EXTRA_INET_CONDITION = "inetCondition";
@@ -120,13 +120,12 @@
      * <p>
      * If an application uses the network in the background, it should listen
      * for this broadcast and stop using the background data if the value is
-     * false.
+     * {@code false}.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
             "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
 
-
     /**
      * Broadcast Action: The network connection may not be good
      * uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and
@@ -255,7 +254,7 @@
 
     public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
 
-    private IConnectivityManager mService;
+    private final IConnectivityManager mService;
 
     static public boolean isNetworkTypeValid(int networkType) {
         return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
@@ -284,6 +283,15 @@
         }
     }
 
+    /** {@hide} */
+    public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+        try {
+            return mService.getActiveNetworkInfoForUid(uid);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
     public NetworkInfo getNetworkInfo(int networkType) {
         try {
             return mService.getNetworkInfo(networkType);
@@ -300,7 +308,7 @@
         }
     }
 
-    /** @hide */
+    /** {@hide} */
     public LinkProperties getActiveLinkProperties() {
         try {
             return mService.getActiveLinkProperties();
@@ -309,7 +317,7 @@
         }
     }
 
-    /** @hide */
+    /** {@hide} */
     public LinkProperties getLinkProperties(int networkType) {
         try {
             return mService.getLinkProperties(networkType);
@@ -479,19 +487,11 @@
     }
 
     /**
-     * Don't allow use of default constructor.
-     */
-    @SuppressWarnings({"UnusedDeclaration"})
-    private ConnectivityManager() {
-    }
-
-    /**
      * {@hide}
      */
     public ConnectivityManager(IConnectivityManager service) {
         if (service == null) {
-            throw new IllegalArgumentException(
-                "ConnectivityManager() cannot be constructed with null service");
+            throw new IllegalArgumentException("missing IConnectivityManager");
         }
         mService = service;
     }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 8be492c..647a60a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -33,13 +33,11 @@
     int getNetworkPreference();
 
     NetworkInfo getActiveNetworkInfo();
-
+    NetworkInfo getActiveNetworkInfoForUid(int uid);
     NetworkInfo getNetworkInfo(int networkType);
-
     NetworkInfo[] getAllNetworkInfo();
 
     LinkProperties getActiveLinkProperties();
-
     LinkProperties getLinkProperties(int networkType);
 
     boolean setRadios(boolean onOff);
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
new file mode 100644
index 0000000..9230151
--- /dev/null
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** {@hide} */
+oneway interface INetworkPolicyListener {
+
+    void onRulesChanged(int uid, int uidRules);
+
+}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index d9351ee..c1f3530 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.net.INetworkPolicyListener;
+
 /**
  * Interface that creates and modifies network policy rules.
  *
@@ -26,6 +28,11 @@
     void setUidPolicy(int uid, int policy);
     int getUidPolicy(int uid);
 
+    boolean isUidForeground(int uid);
+
+    void registerListener(INetworkPolicyListener listener);
+    void unregisterListener(INetworkPolicyListener listener);
+
     // TODO: build API to surface stats details for settings UI
 
 }
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
new file mode 100644
index 0000000..6d57036
--- /dev/null
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.NetworkStatsHistory;
+
+/** {@hide} */
+interface INetworkStatsService {
+
+    NetworkStatsHistory[] getNetworkStatsSummary(int networkType);
+    NetworkStatsHistory getNetworkStatsUid(int uid);
+
+}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 5f5e11c..537750a 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -74,7 +74,9 @@
         /** IP traffic not available. */
         DISCONNECTED,
         /** Attempt to connect failed. */
-        FAILED
+        FAILED,
+        /** Access to this network is blocked. */
+        BLOCKED
     }
 
     /**
@@ -96,6 +98,7 @@
         stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
         stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
         stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
+        stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED);
     }
 
     private int mNetworkType;
@@ -138,6 +141,23 @@
         mIsRoaming = false;
     }
 
+    /** {@hide} */
+    public NetworkInfo(NetworkInfo source) {
+        if (source != null) {
+            mNetworkType = source.mNetworkType;
+            mSubtype = source.mSubtype;
+            mTypeName = source.mTypeName;
+            mSubtypeName = source.mSubtypeName;
+            mState = source.mState;
+            mDetailedState = source.mDetailedState;
+            mReason = source.mReason;
+            mExtraInfo = source.mExtraInfo;
+            mIsFailover = source.mIsFailover;
+            mIsRoaming = source.mIsRoaming;
+            mIsAvailable = source.mIsAvailable;
+        }
+    }
+
     /**
      * Reports the type of network (currently mobile or Wi-Fi) to which the
      * info in this object pertains.
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 1913aa7..dd7c1b0 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.os.RemoteException;
 
+import java.io.PrintWriter;
+
 /**
  * Manager for creating and modifying network policy rules.
  *
@@ -28,12 +30,13 @@
 
     /** No specific network policy, use system default. */
     public static final int POLICY_NONE = 0x0;
-    /** Reject network usage when application in background. */
-    public static final int POLICY_REJECT_BACKGROUND = 0x1;
-    /** Reject network usage on paid network connections. */
-    public static final int POLICY_REJECT_PAID = 0x2;
-    /** Application should conserve data. */
-    public static final int POLICY_CONSERVE_DATA = 0x4;
+    /** Reject network usage on paid networks when application in background. */
+    public static final int POLICY_REJECT_PAID_BACKGROUND = 0x1;
+
+    /** All network traffic should be allowed. */
+    public static final int RULE_ALLOW_ALL = 0x0;
+    /** Reject traffic on paid networks. */
+    public static final int RULE_REJECT_PAID = 0x1;
 
     private INetworkPolicyManager mService;
 
@@ -51,9 +54,8 @@
     /**
      * Set policy flags for specific UID.
      *
-     * @param policy {@link #POLICY_NONE} or combination of
-     *            {@link #POLICY_REJECT_BACKGROUND}, {@link #POLICY_REJECT_PAID},
-     *            or {@link #POLICY_CONSERVE_DATA}.
+     * @param policy {@link #POLICY_NONE} or combination of flags like
+     *            {@link #POLICY_REJECT_PAID_BACKGROUND}.
      */
     public void setUidPolicy(int uid, int policy) {
         try {
@@ -69,5 +71,23 @@
             return POLICY_NONE;
         }
     }
+    
+    /** {@hide} */
+    public static void dumpPolicy(PrintWriter fout, int policy) {
+        fout.write("[");
+        if ((policy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
+            fout.write("REJECT_PAID_BACKGROUND");
+        }
+        fout.write("]");
+    }
+
+    /** {@hide} */
+    public static void dumpRules(PrintWriter fout, int rules) {
+        fout.write("[");
+        if ((rules & RULE_REJECT_PAID) != 0) {
+            fout.write("REJECT_PAID");
+        }
+        fout.write("]");
+    }
 
 }
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 0f207bc..588bf64 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -22,19 +22,22 @@
 
 import java.io.CharArrayWriter;
 import java.io.PrintWriter;
+import java.util.HashSet;
 
 /**
- * Collection of network statistics. Can contain summary details across all
- * interfaces, or details with per-UID granularity. Designed to parcel quickly
- * across process boundaries.
+ * Collection of active network statistics. Can contain summary details across
+ * all interfaces, or details with per-UID granularity. Internally stores data
+ * as a large table, closely matching {@code /proc/} data format. This structure
+ * optimizes for rapid in-memory comparison, but consider using
+ * {@link NetworkStatsHistory} when persisting.
  *
  * @hide
  */
 public class NetworkStats implements Parcelable {
-    /** {@link #iface} value when entry is summarized over all interfaces. */
+    /** {@link #iface} value when interface details unavailable. */
     public static final String IFACE_ALL = null;
-    /** {@link #uid} value when entry is summarized over all UIDs. */
-    public static final int UID_ALL = 0;
+    /** {@link #uid} value when UID details unavailable. */
+    public static final int UID_ALL = -1;
 
     // NOTE: data should only be accounted for once in this structure; if data
     // is broken out, the summarized version should not be included.
@@ -49,7 +52,7 @@
     public final long[] rx;
     public final long[] tx;
 
-    // TODO: add fg/bg stats and tag granularity
+    // TODO: add fg/bg stats once reported by kernel
 
     private NetworkStats(long elapsedRealtime, String[] iface, int[] uid, long[] rx, long[] tx) {
         this.elapsedRealtime = elapsedRealtime;
@@ -120,15 +123,35 @@
     }
 
     /**
+     * Return list of unique interfaces known by this data structure.
+     */
+    public String[] getKnownIfaces() {
+        final HashSet<String> ifaces = new HashSet<String>();
+        for (String iface : this.iface) {
+            if (iface != IFACE_ALL) {
+                ifaces.add(iface);
+            }
+        }
+        return ifaces.toArray(new String[ifaces.size()]);
+    }
+
+    /**
      * Subtract the given {@link NetworkStats}, effectively leaving the delta
      * between two snapshots in time. Assumes that statistics rows collect over
      * time, and that none of them have disappeared.
+     *
+     * @param enforceMonotonic Validate that incoming value is strictly
+     *            monotonic compared to this object.
      */
-    public NetworkStats subtract(NetworkStats value) {
-        // result will have our rows, but no meaningful timestamp
-        final int length = length();
-        final NetworkStats.Builder result = new NetworkStats.Builder(-1, length);
+    public NetworkStats subtract(NetworkStats value, boolean enforceMonotonic) {
+        final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
+        if (enforceMonotonic && deltaRealtime < 0) {
+            throw new IllegalArgumentException("found non-monotonic realtime");
+        }
 
+        // result will have our rows, and elapsed time between snapshots
+        final int length = length();
+        final NetworkStats.Builder result = new NetworkStats.Builder(deltaRealtime, length);
         for (int i = 0; i < length; i++) {
             final String iface = this.iface[i];
             final int uid = this.uid[i];
@@ -142,6 +165,9 @@
                 // existing row, subtract remote value
                 final long rx = this.rx[i] - value.rx[j];
                 final long tx = this.tx[i] - value.tx[j];
+                if (enforceMonotonic && (rx < 0 || tx < 0)) {
+                    throw new IllegalArgumentException("found non-monotonic values");
+                }
                 result.addEntry(iface, uid, rx, tx);
             }
         }
diff --git a/core/java/android/net/NetworkStatsHistory.aidl b/core/java/android/net/NetworkStatsHistory.aidl
new file mode 100644
index 0000000..8b9069f
--- /dev/null
+++ b/core/java/android/net/NetworkStatsHistory.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable NetworkStatsHistory;
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
new file mode 100644
index 0000000..b16101f
--- /dev/null
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.CharArrayWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Collection of historical network statistics, recorded into equally-sized
+ * "buckets" in time. Internally it stores data in {@code long} series for more
+ * efficient persistence.
+ * <p>
+ * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
+ * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
+ * sorted at all times.
+ *
+ * @hide
+ */
+public class NetworkStatsHistory implements Parcelable {
+    private static final int VERSION = 1;
+
+    /** {@link #uid} value when UID details unavailable. */
+    public static final int UID_ALL = -1;
+
+    // TODO: teach about zigzag encoding to use less disk space
+    // TODO: teach how to convert between bucket sizes
+
+    public final int networkType;
+    public final String identity;
+    public final int uid;
+    public final long bucketDuration;
+
+    int bucketCount;
+    long[] bucketStart;
+    long[] rx;
+    long[] tx;
+
+    public NetworkStatsHistory(int networkType, String identity, int uid, long bucketDuration) {
+        this.networkType = networkType;
+        this.identity = identity;
+        this.uid = uid;
+        this.bucketDuration = bucketDuration;
+        bucketStart = new long[0];
+        rx = new long[0];
+        tx = new long[0];
+        bucketCount = bucketStart.length;
+    }
+
+    public NetworkStatsHistory(Parcel in) {
+        networkType = in.readInt();
+        identity = in.readString();
+        uid = in.readInt();
+        bucketDuration = in.readLong();
+        bucketStart = readLongArray(in);
+        rx = in.createLongArray();
+        tx = in.createLongArray();
+        bucketCount = bucketStart.length;
+    }
+
+    /** {@inheritDoc} */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(networkType);
+        out.writeString(identity);
+        out.writeInt(uid);
+        out.writeLong(bucketDuration);
+        writeLongArray(out, bucketStart, bucketCount);
+        writeLongArray(out, rx, bucketCount);
+        writeLongArray(out, tx, bucketCount);
+    }
+
+    public NetworkStatsHistory(DataInputStream in) throws IOException {
+        final int version = in.readInt();
+        networkType = in.readInt();
+        identity = in.readUTF();
+        uid = in.readInt();
+        bucketDuration = in.readLong();
+        bucketStart = readLongArray(in);
+        rx = readLongArray(in);
+        tx = readLongArray(in);
+        bucketCount = bucketStart.length;
+    }
+
+    public void writeToStream(DataOutputStream out) throws IOException {
+        out.writeInt(VERSION);
+        out.writeInt(networkType);
+        out.writeUTF(identity);
+        out.writeInt(uid);
+        out.writeLong(bucketDuration);
+        writeLongArray(out, bucketStart, bucketCount);
+        writeLongArray(out, rx, bucketCount);
+        writeLongArray(out, tx, bucketCount);
+    }
+
+    /** {@inheritDoc} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Record that data traffic occurred in the given time range. Will
+     * distribute across internal buckets, creating new buckets as needed.
+     */
+    public void recordData(long start, long end, long rx, long tx) {
+        // create any buckets needed by this range
+        ensureBuckets(start, end);
+
+        // distribute data usage into buckets
+        final long duration = end - start;
+        for (int i = bucketCount - 1; i >= 0; i--) {
+            final long curStart = bucketStart[i];
+            final long curEnd = curStart + bucketDuration;
+
+            // bucket is older than record; we're finished
+            if (curEnd < start) break;
+            // bucket is newer than record; keep looking
+            if (curStart > end) continue;
+
+            final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
+            if (overlap > 0) {
+                this.rx[i] += rx * overlap / duration;
+                this.tx[i] += tx * overlap / duration;
+            }
+        }
+    }
+
+    /**
+     * Ensure that buckets exist for given time range, creating as needed.
+     */
+    private void ensureBuckets(long start, long end) {
+        // normalize incoming range to bucket boundaries
+        start -= start % bucketDuration;
+        end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
+
+        for (long now = start; now < end; now += bucketDuration) {
+            // try finding existing bucket
+            final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
+            if (index < 0) {
+                // bucket missing, create and insert
+                insertBucket(~index, now);
+            }
+        }
+    }
+
+    /**
+     * Insert new bucket at requested index and starting time.
+     */
+    private void insertBucket(int index, long start) {
+        // create more buckets when needed
+        if (bucketCount + 1 > bucketStart.length) {
+            final int newLength = bucketStart.length + 10;
+            bucketStart = Arrays.copyOf(bucketStart, newLength);
+            rx = Arrays.copyOf(rx, newLength);
+            tx = Arrays.copyOf(tx, newLength);
+        }
+
+        // create gap when inserting bucket in middle
+        if (index < bucketCount) {
+            final int dstPos = index + 1;
+            final int length = bucketCount - index;
+
+            System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
+            System.arraycopy(rx, index, rx, dstPos, length);
+            System.arraycopy(tx, index, tx, dstPos, length);
+        }
+
+        bucketStart[index] = start;
+        rx[index] = 0;
+        tx[index] = 0;
+        bucketCount++;
+    }
+
+    /**
+     * Remove buckets older than requested cutoff.
+     */
+    public void removeBucketsBefore(long cutoff) {
+        int i;
+        for (i = 0; i < bucketCount; i++) {
+            final long curStart = bucketStart[i];
+            final long curEnd = curStart + bucketDuration;
+
+            // cutoff happens before or during this bucket; everything before
+            // this bucket should be removed.
+            if (curEnd > cutoff) break;
+        }
+
+        if (i > 0) {
+            final int length = bucketStart.length;
+            bucketStart = Arrays.copyOfRange(bucketStart, i, length);
+            rx = Arrays.copyOfRange(rx, i, length);
+            tx = Arrays.copyOfRange(tx, i, length);
+            bucketCount -= i;
+        }
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        // TODO: consider stripping identity when dumping
+        pw.print(prefix);
+        pw.print("NetworkStatsHistory: networkType="); pw.print(networkType);
+        pw.print(" identity="); pw.print(identity);
+        pw.print(" uid="); pw.println(uid);
+        for (int i = 0; i < bucketCount; i++) {
+            pw.print(prefix);
+            pw.print("  timestamp="); pw.print(bucketStart[i]);
+            pw.print(" rx="); pw.print(rx[i]);
+            pw.print(" tx="); pw.println(tx[i]);
+        }
+    }
+
+    @Override
+    public String toString() {
+        final CharArrayWriter writer = new CharArrayWriter();
+        dump("", new PrintWriter(writer));
+        return writer.toString();
+    }
+
+    public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
+        public NetworkStatsHistory createFromParcel(Parcel in) {
+            return new NetworkStatsHistory(in);
+        }
+
+        public NetworkStatsHistory[] newArray(int size) {
+            return new NetworkStatsHistory[size];
+        }
+    };
+
+    private static long[] readLongArray(DataInputStream in) throws IOException {
+        final int size = in.readInt();
+        final long[] values = new long[size];
+        for (int i = 0; i < values.length; i++) {
+            values[i] = in.readLong();
+        }
+        return values;
+    }
+
+    private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException {
+        if (size > values.length) {
+            throw new IllegalArgumentException("size larger than length");
+        }
+        out.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            out.writeLong(values[i]);
+        }
+    }
+
+    private static long[] readLongArray(Parcel in) {
+        final int size = in.readInt();
+        final long[] values = new long[size];
+        for (int i = 0; i < values.length; i++) {
+            values[i] = in.readLong();
+        }
+        return values;
+    }
+
+    private static void writeLongArray(Parcel out, long[] values, int size) {
+        if (size > values.length) {
+            throw new IllegalArgumentException("size larger than length");
+        }
+        out.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            out.writeLong(values[i]);
+        }
+    }
+
+}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index c0ff734..8ab64fa 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -141,7 +141,8 @@
 
             // subtract starting values and return delta
             final NetworkStats profilingStop = getNetworkStatsForUid(context);
-            final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart);
+            final NetworkStats profilingDelta = profilingStop.subtract(
+                    sActiveProfilingStart, false);
             sActiveProfilingStart = null;
             return profilingDelta;
         }
diff --git a/core/java/android/os/INetStatService.aidl b/core/java/android/os/INetStatService.aidl
deleted file mode 100644
index a8f3de0..0000000
--- a/core/java/android/os/INetStatService.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-/**
- * Retrieves packet and byte counts for the phone data interface,
- * and for all interfaces.
- * Used for the data activity icon and the phone status in Settings.
- *
- * {@hide}
- */
-interface INetStatService {
-    long getMobileTxPackets();
-    long getMobileRxPackets();
-    long getMobileTxBytes();
-    long getMobileRxBytes();
-    long getTotalTxPackets();
-    long getTotalRxPackets();
-    long getTotalTxBytes();
-    long getTotalRxBytes();
-}
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index 780c52e..dc68264 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -51,6 +51,13 @@
      * it also has an icon specified.
      */
     public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
+
+    /**
+     * This item's action view collapses to a normal menu item.
+     * When expanded, the action view temporarily takes over
+     * a larger segment of its container.
+     */
+    public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
     
     /**
      * Interface definition for a callback to be invoked when a menu item is
@@ -74,6 +81,34 @@
     }
 
     /**
+     * Interface definition for a callback to be invoked when a menu item
+     * marked with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} is
+     * expanded or collapsed.
+     *
+     * @see MenuItem#expandActionView()
+     * @see MenuItem#collapseActionView()
+     * @see MenuItem#setShowAsActionFlags(int)
+     * @see MenuItem#
+     */
+    public interface OnActionExpandListener {
+        /**
+         * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
+         * is expanded.
+         * @param item Item that was expanded
+         * @return true if the item should expand, false if expansion should be suppressed.
+         */
+        public boolean onMenuItemActionExpand(MenuItem item);
+
+        /**
+         * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
+         * is collapsed.
+         * @param item Item that was collapsed
+         * @return true if the item should collapse, false if collapsing should be suppressed.
+         */
+        public boolean onMenuItemActionCollapse(MenuItem item);
+    }
+
+    /**
      * Return the identifier for this menu item.  The identifier can not
      * be changed after the menu is created.
      *
@@ -421,6 +456,27 @@
     public void setShowAsAction(int actionEnum);
 
     /**
+     * Sets how this item should display in the presence of an Action Bar.
+     * The parameter actionEnum is a flag set. One of {@link #SHOW_AS_ACTION_ALWAYS},
+     * {@link #SHOW_AS_ACTION_IF_ROOM}, or {@link #SHOW_AS_ACTION_NEVER} should
+     * be used, and you may optionally OR the value with {@link #SHOW_AS_ACTION_WITH_TEXT}.
+     * SHOW_AS_ACTION_WITH_TEXT requests that when the item is shown as an action,
+     * it should be shown with a text label.
+     *
+     * <p>Note: This method differs from {@link #setShowAsAction(int)} only in that it
+     * returns the current MenuItem instance for call chaining.
+     *
+     * @param actionEnum How the item should display. One of
+     * {@link #SHOW_AS_ACTION_ALWAYS}, {@link #SHOW_AS_ACTION_IF_ROOM}, or
+     * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default.
+     *
+     * @see android.app.ActionBar
+     * @see #setActionView(View)
+     * @return This MenuItem instance for call chaining.
+     */
+    public MenuItem setShowAsActionFlags(int actionEnum);
+
+    /**
      * Set an action view for this menu item. An action view will be displayed in place
      * of an automatically generated menu item element in the UI when this item is shown
      * as an action within a parent.
@@ -453,4 +509,52 @@
      * @see #setShowAsAction(int)
      */
     public View getActionView();
+
+    /**
+     * Expand the action view associated with this menu item.
+     * The menu item must have an action view set, as well as
+     * the showAsAction flag {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
+     * If a listener has been set using {@link #setOnActionExpandListener(OnActionExpandListener)}
+     * it will have its {@link OnActionExpandListener#onMenuItemActionExpand(MenuItem)}
+     * method invoked. The listener may return false from this method to prevent expanding
+     * the action view.
+     *
+     * @return true if the action view was expanded, false otherwise.
+     */
+    public boolean expandActionView();
+
+    /**
+     * Collapse the action view associated with this menu item.
+     * The menu item must have an action view set, as well as the showAsAction flag
+     * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}. If a listener has been set using
+     * {@link #setOnActionExpandListener(OnActionExpandListener)} it will have its
+     * {@link OnActionExpandListener#onMenuItemActionCollapse(MenuItem)} method invoked.
+     * The listener may return false from this method to prevent collapsing the action view.
+     *
+     * @return true if the action view was collapsed, false otherwise.
+     */
+    public boolean collapseActionView();
+
+    /**
+     * Returns true if this menu item's action view has been expanded.
+     *
+     * @return true if the item's action view is expanded, false otherwise.
+     *
+     * @see #expandActionView()
+     * @see #collapseActionView()
+     * @see #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
+     * @see OnActionExpandListener
+     */
+    public boolean isActionViewExpanded();
+
+    /**
+     * Set an {@link OnActionExpandListener} on this menu item to be notified when
+     * the associated action view is expanded or collapsed. The menu item must
+     * be configured to expand or collapse its action view using the flag
+     * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
+     *
+     * @param listener Listener that will respond to expand/collapse events
+     * @return This menu item instance for call chaining
+     */
+    public MenuItem setOnActionExpandListener(OnActionExpandListener listener);
 }
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 1f7441d..fee69e0 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -145,7 +145,9 @@
                             a.getString(com.android.internal.R.styleable
                                     .InputMethod_Subtype_imeSubtypeMode),
                             a.getString(com.android.internal.R.styleable
-                                    .InputMethod_Subtype_imeSubtypeExtraValue));
+                                    .InputMethod_Subtype_imeSubtypeExtraValue),
+                            a.getBoolean(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_isAuxiliary, false));
                     mSubtypes.add(subtype);
                 }
             }
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index de38fbe..20cf93d 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -17,8 +17,10 @@
 package android.view.inputmethod;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import java.util.ArrayList;
@@ -137,6 +139,31 @@
         return mIsAuxiliary;
     }
 
+    /**
+     * @param context Context will be used for getting Locale and PackageManager.
+     * @param packageName The package name of the IME
+     * @param appInfo The application info of the IME
+     * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
+     * can have only one %s in it. If there is, the %s part will be replaced with the locale's
+     * display name by the formatter. If there is not, this method simply returns the string
+     * specified by mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the
+     * framework to generate an appropriate display name.
+     */
+    public CharSequence getDisplayName(
+            Context context, String packageName, ApplicationInfo appInfo) {
+        final String locale = context.getResources().getConfiguration().locale.getDisplayName();
+        if (mSubtypeNameResId == 0) {
+            return locale;
+        }
+        final String subtypeName = context.getPackageManager().getText(
+                packageName, mSubtypeNameResId, appInfo).toString();
+        if (!TextUtils.isEmpty(subtypeName)) {
+            return String.format(subtypeName, locale);
+        } else {
+            return locale;
+        }
+    }
+
     private HashMap<String, String> getExtraValueHashMap() {
         if (mExtraValueHashMapCache == null) {
             mExtraValueHashMapCache = new HashMap<String, String>();
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index 2a1398d..4ee16e7 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import java.util.ArrayList;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
@@ -23,14 +25,12 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
-import android.view.Gravity;
 import android.widget.RemoteViews.RemoteView;
 
-import java.util.ArrayList;
-
 
 /**
  * FrameLayout is designed to block out an area on the screen to display
@@ -364,10 +364,10 @@
                     gravity = DEFAULT_CHILD_GRAVITY;
                 }
 
-                final int horizontalGravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
+                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
                 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
 
-                switch (horizontalGravity) {
+                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                     case Gravity.LEFT:
                         childLeft = parentLeft + lp.leftMargin;
                         break;
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 8d449e0..298fe00 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -1430,8 +1430,8 @@
                 if (gravity < 0) {
                     gravity = minorGravity;
                 }
-                gravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
-                switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
+                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                     case Gravity.CENTER_HORIZONTAL:
                         childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                 + lp.leftMargin - lp.rightMargin;
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index 0ef4861..a4bcf60 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -236,4 +236,31 @@
     public MenuItem setActionView(int resId) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public MenuItem setShowAsActionFlags(int actionEnum) {
+        setShowAsAction(actionEnum);
+        return this;
+    }
+
+    @Override
+    public boolean expandActionView() {
+        return false;
+    }
+
+    @Override
+    public boolean collapseActionView() {
+        return false;
+    }
+
+    @Override
+    public boolean isActionViewExpanded() {
+        return false;
+    }
+
+    @Override
+    public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+        // No need to save the listener; ActionMenuItem does not support collapsing items.
+        return this;
+    }
 }
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
index 0051ec3..91dd7e36 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -112,8 +112,11 @@
 
     @Override
     public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
-        final View actionView = item.getActionView();
-        return actionView != null ? actionView : super.getItemView(item, convertView, parent);
+        View actionView = item.getActionView();
+        actionView = actionView != null && !item.hasCollapsibleActionView() ?
+                actionView : super.getItemView(item, convertView, parent);
+        actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
+        return actionView;
     }
 
     @Override
@@ -303,7 +306,7 @@
 
             if (item.requiresActionButton()) {
                 View v = item.getActionView();
-                if (v == null) {
+                if (v == null || item.hasCollapsibleActionView()) {
                     v = getItemView(item, mScrapActionButtonView, parent);
                     if (mScrapActionButtonView == null) {
                         mScrapActionButtonView = v;
@@ -329,7 +332,7 @@
 
                 if (isAction) {
                     View v = item.getActionView();
-                    if (v == null) {
+                    if (v == null || item.hasCollapsibleActionView()) {
                         v = getItemView(item, mScrapActionButtonView, parent);
                         if (mScrapActionButtonView == null) {
                             mScrapActionButtonView = v;
diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
index 16f51fd..ddbb08c 100644
--- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
@@ -192,4 +192,12 @@
     public boolean flagActionItems() {
         return false;
     }
+
+    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
+    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
 }
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
index 2cb2a10..f8d24a3 100644
--- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -159,6 +159,14 @@
         return false;
     }
 
+    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
+    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
     public void saveHierarchyState(Bundle outState) {
         SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
         if (mMenuView != null) {
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index e9fcb23..fdfa954 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -744,11 +744,14 @@
         
         if (itemImpl == null || !itemImpl.isEnabled()) {
             return false;
-        }        
+        }
         
         boolean invoked = itemImpl.invoke();
 
-        if (item.hasSubMenu()) {
+        if (itemImpl.hasCollapsibleActionView()) {
+            invoked |= itemImpl.expandActionView();
+            if (invoked) close(true);
+        } else if (item.hasSubMenu()) {
             close(false);
 
             invoked |= dispatchSubMenuSelected((SubMenuBuilder) item.getSubMenu());
@@ -1081,4 +1084,42 @@
     boolean getOptionalIconsVisible() {
         return mOptionalIconsVisible;
     }
+
+    public boolean expandItemActionView(MenuItemImpl item) {
+        if (mPresenters.isEmpty()) return false;
+
+        boolean expanded = false;
+
+        stopDispatchingItemsChanged();
+        for (WeakReference<MenuPresenter> ref : mPresenters) {
+            final MenuPresenter presenter = ref.get();
+            if (presenter == null) {
+                mPresenters.remove(ref);
+            } else if ((expanded = presenter.expandItemActionView(this, item))) {
+                break;
+            }
+        }
+        startDispatchingItemsChanged();
+
+        return expanded;
+    }
+
+    public boolean collapseItemActionView(MenuItemImpl item) {
+        if (mPresenters.isEmpty()) return false;
+
+        boolean collapsed = false;
+
+        stopDispatchingItemsChanged();
+        for (WeakReference<MenuPresenter> ref : mPresenters) {
+            final MenuPresenter presenter = ref.get();
+            if (presenter == null) {
+                mPresenters.remove(ref);
+            } else if ((collapsed = presenter.collapseItemActionView(this, item))) {
+                break;
+            }
+        }
+        startDispatchingItemsChanged();
+
+        return collapsed;
+    }
 }
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index c6d386d..f2430e4 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -77,6 +77,8 @@
     private int mShowAsAction = SHOW_AS_ACTION_NEVER;
 
     private View mActionView;
+    private OnActionExpandListener mOnActionExpandListener;
+    private boolean mIsActionViewExpanded = false;
 
     /** Used for the icon resource ID if this item does not have an icon */
     static final int NO_ICON = 0;
@@ -561,4 +563,61 @@
     public View getActionView() {
         return mActionView;
     }
+
+    @Override
+    public MenuItem setShowAsActionFlags(int actionEnum) {
+        setShowAsAction(actionEnum);
+        return this;
+    }
+
+    @Override
+    public boolean expandActionView() {
+        if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0 || mActionView == null) {
+            return false;
+        }
+
+        if (mOnActionExpandListener == null ||
+                mOnActionExpandListener.onMenuItemActionExpand(this)) {
+            return mMenu.expandItemActionView(this);
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean collapseActionView() {
+        if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) {
+            return false;
+        }
+        if (mActionView == null) {
+            // We're already collapsed if we have no action view.
+            return true;
+        }
+
+        if (mOnActionExpandListener == null ||
+                mOnActionExpandListener.onMenuItemActionCollapse(this)) {
+            return mMenu.collapseItemActionView(this);
+        }
+
+        return false;
+    }
+
+    @Override
+    public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+        mOnActionExpandListener = listener;
+        return this;
+    }
+
+    public boolean hasCollapsibleActionView() {
+        return (mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0 && mActionView != null;
+    }
+
+    public void setActionViewExpanded(boolean isExpanded) {
+        mIsActionViewExpanded = isExpanded;
+        mMenu.onItemsChanged(false);
+    }
+
+    public boolean isActionViewExpanded() {
+        return mIsActionViewExpanded;
+    }
 }
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 38cec29..5767519 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -250,6 +250,14 @@
         return false;
     }
 
+    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
+    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
     private class MenuAdapter extends BaseAdapter {
         private MenuBuilder mAdapterMenu;
 
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
index 5baf419..bd66448 100644
--- a/core/java/com/android/internal/view/menu/MenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -107,4 +107,22 @@
      * @return true if this presenter changed the action status of any items.
      */
     public boolean flagActionItems();
+
+    /**
+     * Called when a menu item with a collapsable action view should expand its action view.
+     *
+     * @param menu Menu containing the item to be expanded
+     * @param item Item to be expanded
+     * @return true if this presenter expanded the action view, false otherwise.
+     */
+    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item);
+
+    /**
+     * Called when a menu item with a collapsable action view should collapse its action view.
+     *
+     * @param menu Menu containing the item to be collapsed
+     * @param item Item to be collapsed
+     * @return true if this presenter collapsed the action view, false otherwise.
+     */
+    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item);
 }
diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
index 834041f..fb1cd5e 100644
--- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
@@ -111,4 +111,14 @@
     public SubMenu setHeaderView(View view) {
         return (SubMenu) super.setHeaderViewInt(view);
     }
+
+    @Override
+    public boolean expandItemActionView(MenuItemImpl item) {
+        return mParentMenu.expandItemActionView(item);
+    }
+
+    @Override
+    public boolean collapseItemActionView(MenuItemImpl item) {
+        return mParentMenu.collapseItemActionView(item);
+    }
 }
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index 788883b..ccbce3e 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -30,7 +30,7 @@
 
 public abstract class AbsActionBarView extends ViewGroup {
     protected ActionMenuView mMenuView;
-    protected ActionMenuPresenter mMenuPresenter;
+    protected ActionMenuPresenter mActionMenuPresenter;
     protected ActionBarContainer mSplitView;
 
     protected Animator mVisibilityAnim;
@@ -108,8 +108,8 @@
     }
 
     public boolean showOverflowMenu() {
-        if (mMenuPresenter != null) {
-            return mMenuPresenter.showOverflowMenu();
+        if (mActionMenuPresenter != null) {
+            return mActionMenuPresenter.showOverflowMenu();
         }
         return false;
     }
@@ -123,26 +123,26 @@
     }
 
     public boolean hideOverflowMenu() {
-        if (mMenuPresenter != null) {
-            return mMenuPresenter.hideOverflowMenu();
+        if (mActionMenuPresenter != null) {
+            return mActionMenuPresenter.hideOverflowMenu();
         }
         return false;
     }
 
     public boolean isOverflowMenuShowing() {
-        if (mMenuPresenter != null) {
-            return mMenuPresenter.isOverflowMenuShowing();
+        if (mActionMenuPresenter != null) {
+            return mActionMenuPresenter.isOverflowMenuShowing();
         }
         return false;
     }
 
     public boolean isOverflowReserved() {
-        return mMenuPresenter != null && mMenuPresenter.isOverflowReserved();
+        return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
     }
 
     public void dismissPopupMenus() {
-        if (mMenuPresenter != null) {
-            mMenuPresenter.dismissPopupMenus();
+        if (mActionMenuPresenter != null) {
+            mActionMenuPresenter.dismissPopupMenus();
         }
     }
 
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index deed1c5..e4c4989 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -167,9 +167,9 @@
         });
 
         final MenuBuilder menu = (MenuBuilder) mode.getMenu();
-        mMenuPresenter = new ActionMenuPresenter();
-        menu.addMenuPresenter(mMenuPresenter);
-        mMenuView = (ActionMenuView) mMenuPresenter.getMenuView(this);
+        mActionMenuPresenter = new ActionMenuPresenter();
+        menu.addMenuPresenter(mActionMenuPresenter);
+        mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
 
         final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
                 LayoutParams.MATCH_PARENT);
@@ -178,10 +178,10 @@
             addView(mMenuView);
         } else {
             // Allow full screen width in split mode.
-            mMenuPresenter.setWidthLimit(
+            mActionMenuPresenter.setWidthLimit(
                     getContext().getResources().getDisplayMetrics().widthPixels, true);
             // No limit to the item count; use whatever will fit.
-            mMenuPresenter.setItemLimit(Integer.MAX_VALUE);
+            mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
             // Span the whole width
             layoutParams.width = LayoutParams.MATCH_PARENT;
             mSplitView.addView(mMenuView);
@@ -227,24 +227,24 @@
 
     @Override
     public boolean showOverflowMenu() {
-        if (mMenuPresenter != null) {
-            return mMenuPresenter.showOverflowMenu();
+        if (mActionMenuPresenter != null) {
+            return mActionMenuPresenter.showOverflowMenu();
         }
         return false;
     }
 
     @Override
     public boolean hideOverflowMenu() {
-        if (mMenuPresenter != null) {
-            return mMenuPresenter.hideOverflowMenu();
+        if (mActionMenuPresenter != null) {
+            return mActionMenuPresenter.hideOverflowMenu();
         }
         return false;
     }
 
     @Override
     public boolean isOverflowMenuShowing() {
-        if (mMenuPresenter != null) {
-            return mMenuPresenter.isOverflowMenuShowing();
+        if (mActionMenuPresenter != null) {
+            return mActionMenuPresenter.isOverflowMenuShowing();
         }
         return false;
     }
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index b7aac14..eb97ea8 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -21,7 +21,10 @@
 import com.android.internal.view.menu.ActionMenuPresenter;
 import com.android.internal.view.menu.ActionMenuView;
 import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
 import com.android.internal.view.menu.MenuPresenter;
+import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.SubMenuBuilder;
 
 import android.app.ActionBar;
 import android.app.ActionBar.OnNavigationListener;
@@ -33,21 +36,22 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.TextUtils;
-import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.Window;
 import android.widget.AdapterView;
 import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
@@ -87,9 +91,8 @@
     private Drawable mIcon;
     private Drawable mLogo;
 
-    private View mHomeLayout;
-    private View mHomeAsUpView;
-    private ImageView mIconView;
+    private HomeView mHomeLayout;
+    private HomeView mExpandedHomeLayout;
     private LinearLayout mTitleLayout;
     private TextView mTitleView;
     private TextView mSubtitleView;
@@ -124,6 +127,9 @@
 
     private Runnable mTabSelector;
 
+    private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
+    View mExpandedActionView;
+
     private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
             new AdapterView.OnItemSelectedListener() {
         public void onItemSelected(AdapterView parent, View view, int position, long id) {
@@ -136,7 +142,15 @@
         }
     };
 
-    private OnClickListener mTabClickListener = null;
+    private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final MenuItemImpl item = mExpandedMenuPresenter.mCurrentExpandedItem;
+            if (item != null) {
+                item.collapseActionView();
+            }
+        }
+    };
 
     public ActionBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -187,10 +201,11 @@
                 com.android.internal.R.styleable.ActionBar_homeLayout,
                 com.android.internal.R.layout.action_bar_home);
 
-        mHomeLayout = inflater.inflate(homeResId, this, false);
+        mHomeLayout = (HomeView) inflater.inflate(homeResId, this, false);
 
-        mHomeAsUpView = mHomeLayout.findViewById(com.android.internal.R.id.up);
-        mIconView = (ImageView) mHomeLayout.findViewById(com.android.internal.R.id.home);
+        mExpandedHomeLayout = (HomeView) inflater.inflate(homeResId, this, false);
+        mExpandedHomeLayout.setUp(true);
+        mExpandedHomeLayout.setOnClickListener(mExpandedActionViewUpListener);
         
         mTitleStyleRes = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
         mSubtitleStyleRes = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
@@ -285,7 +300,7 @@
     public void setEmbeddedTabView(ScrollingTabContainerView tabs) {
         mTabScrollView = tabs;
         mIncludeTabs = tabs != null;
-        if (mIncludeTabs) {
+        if (mIncludeTabs && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
             addView(mTabScrollView);
         }
     }
@@ -298,7 +313,8 @@
         if (menu == mOptionsMenu) return;
 
         if (mOptionsMenu != null) {
-            mOptionsMenu.removeMenuPresenter(mMenuPresenter);
+            mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
+            mOptionsMenu.removeMenuPresenter(mExpandedMenuPresenter);
         }
 
         MenuBuilder builder = (MenuBuilder) menu;
@@ -306,13 +322,15 @@
         if (mMenuView != null) {
             removeView(mMenuView);
         }
-        if (mMenuPresenter == null) {
-            mMenuPresenter = new ActionMenuPresenter();
-            mMenuPresenter.setCallback(cb);
+        if (mActionMenuPresenter == null) {
+            mActionMenuPresenter = new ActionMenuPresenter();
+            mActionMenuPresenter.setCallback(cb);
+            mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
         }
-        builder.addMenuPresenter(mMenuPresenter);
+        builder.addMenuPresenter(mActionMenuPresenter);
+        builder.addMenuPresenter(mExpandedMenuPresenter);
 
-        final ActionMenuView menuView = (ActionMenuView) mMenuPresenter.getMenuView(this);
+        final ActionMenuView menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
         final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
                 LayoutParams.MATCH_PARENT);
         menuView.setLayoutParams(layoutParams);
@@ -320,10 +338,10 @@
             addView(menuView);
         } else {
             // Allow full screen width in split mode.
-            mMenuPresenter.setWidthLimit(
+            mActionMenuPresenter.setWidthLimit(
                     getContext().getResources().getDisplayMetrics().widthPixels, true);
             // No limit to the item count; use whatever will fit.
-            mMenuPresenter.setItemLimit(Integer.MAX_VALUE);
+            mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
             // Span the whole width
             layoutParams.width = LayoutParams.MATCH_PARENT;
             if (mSplitView != null) {
@@ -411,13 +429,12 @@
             mHomeLayout.setVisibility(vis);
 
             if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
-                final boolean isUp = (options & ActionBar.DISPLAY_HOME_AS_UP) != 0;
-                mHomeAsUpView.setVisibility(isUp ? VISIBLE : GONE);
+                mHomeLayout.setUp((options & ActionBar.DISPLAY_HOME_AS_UP) != 0);
             }
 
             if ((flagsChanged & ActionBar.DISPLAY_USE_LOGO) != 0) {
                 final boolean logoVis = mLogo != null && (options & ActionBar.DISPLAY_USE_LOGO) != 0;
-                mIconView.setImageDrawable(logoVis ? mLogo : mIcon);
+                mHomeLayout.setIcon(logoVis ? mLogo : mIcon);
             }
 
             if ((flagsChanged & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
@@ -457,7 +474,7 @@
         mIcon = icon;
         if (icon != null &&
                 ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) == 0 || mLogo == null)) {
-            mIconView.setImageDrawable(icon);
+            mHomeLayout.setIcon(icon);
         }
     }
 
@@ -468,7 +485,7 @@
     public void setLogo(Drawable logo) {
         mLogo = logo;
         if (logo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
-            mIconView.setImageDrawable(logo);
+            mHomeLayout.setIcon(logo);
         }
     }
 
@@ -482,7 +499,7 @@
     private int getPreferredIconDensity() {
         final Resources res = mContext.getResources();
         final int availableHeight = getLayoutParams().height -
-                mIconView.getPaddingTop() - mIconView.getPaddingBottom();
+                mHomeLayout.getVerticalIconPadding();
         int iconSize = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
 
         if (iconSize * DisplayMetrics.DENSITY_LOW >= availableHeight) {
@@ -500,7 +517,7 @@
         if (mode != oldMode) {
             switch (oldMode) {
             case ActionBar.NAVIGATION_MODE_LIST:
-                if (mSpinner != null) {
+                if (mListNavLayout != null) {
                     removeView(mListNavLayout);
                 }
                 break;
@@ -685,10 +702,13 @@
         int leftOfCenter = availableWidth / 2;
         int rightOfCenter = leftOfCenter;
 
-        if (mHomeLayout.getVisibility() != GONE) {
-            mHomeLayout.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+        View homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
+
+        if (homeLayout.getVisibility() != GONE) {
+            homeLayout.measure(
+                    MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
                     MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
-            final int homeWidth = mHomeLayout.getMeasuredWidth();
+            final int homeWidth = homeLayout.getMeasuredWidth();
             availableWidth = Math.max(0, availableWidth - homeWidth);
             leftOfCenter = Math.max(0, availableWidth - homeWidth);
         }
@@ -699,40 +719,42 @@
             rightOfCenter = Math.max(0, rightOfCenter - mMenuView.getMeasuredWidth());
         }
 
-        boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
-                (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
-        if (showTitle) {
-            availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
-            leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth());
-        }
+        if (mExpandedActionView == null) {
+            boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
+            (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
+            if (showTitle) {
+                availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
+                leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth());
+            }
 
-        switch (mNavigationMode) {
-        case ActionBar.NAVIGATION_MODE_LIST:
-            if (mListNavLayout != null) {
-                final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
-                availableWidth = Math.max(0, availableWidth - itemPaddingSize);
-                leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
-                mListNavLayout.measure(
-                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
-                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
-                final int listNavWidth = mListNavLayout.getMeasuredWidth();
-                availableWidth = Math.max(0, availableWidth - listNavWidth);
-                leftOfCenter = Math.max(0, leftOfCenter - listNavWidth);
+            switch (mNavigationMode) {
+                case ActionBar.NAVIGATION_MODE_LIST:
+                    if (mListNavLayout != null) {
+                        final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
+                        availableWidth = Math.max(0, availableWidth - itemPaddingSize);
+                        leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
+                        mListNavLayout.measure(
+                                MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+                                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+                        final int listNavWidth = mListNavLayout.getMeasuredWidth();
+                        availableWidth = Math.max(0, availableWidth - listNavWidth);
+                        leftOfCenter = Math.max(0, leftOfCenter - listNavWidth);
+                    }
+                    break;
+                case ActionBar.NAVIGATION_MODE_TABS:
+                    if (mTabScrollView != null) {
+                        final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
+                        availableWidth = Math.max(0, availableWidth - itemPaddingSize);
+                        leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
+                        mTabScrollView.measure(
+                                MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+                                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+                        final int tabWidth = mTabScrollView.getMeasuredWidth();
+                        availableWidth = Math.max(0, availableWidth - tabWidth);
+                        leftOfCenter = Math.max(0, leftOfCenter - tabWidth);
+                    }
+                    break;
             }
-            break;
-        case ActionBar.NAVIGATION_MODE_TABS:
-            if (mTabScrollView != null) {
-                final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
-                availableWidth = Math.max(0, availableWidth - itemPaddingSize);
-                leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
-                mTabScrollView.measure(
-                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
-                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
-                final int tabWidth = mTabScrollView.getMeasuredWidth();
-                availableWidth = Math.max(0, availableWidth - tabWidth);
-                leftOfCenter = Math.max(0, leftOfCenter - tabWidth);
-            }
-            break;
         }
 
         if (mIndeterminateProgressView != null &&
@@ -743,8 +765,16 @@
                     rightOfCenter - mIndeterminateProgressView.getMeasuredWidth());
         }
 
-        if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
-            final LayoutParams lp = generateLayoutParams(mCustomNavView.getLayoutParams());
+        View customView = null;
+        if (mExpandedActionView != null) {
+            customView = mExpandedActionView;
+        } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 &&
+                mCustomNavView != null) {
+            customView = mCustomNavView;
+        }
+
+        if (customView != null) {
+            final LayoutParams lp = generateLayoutParams(customView.getLayoutParams());
             final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
                     (ActionBar.LayoutParams) lp : null;
 
@@ -781,7 +811,7 @@
                 customNavWidth = Math.min(leftOfCenter, rightOfCenter) * 2;
             }
 
-            mCustomNavView.measure(
+            customView.measure(
                     MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode),
                     MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode));
         }
@@ -822,31 +852,34 @@
             return;
         }
 
-        if (mHomeLayout.getVisibility() != GONE) {
-            x += positionChild(mHomeLayout, x, y, contentHeight);
-        }
-        
-        final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
-                (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
-        if (showTitle) {
-            x += positionChild(mTitleLayout, x, y, contentHeight);
+        View homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
+        if (homeLayout.getVisibility() != GONE) {
+            x += positionChild(homeLayout, x, y, contentHeight);
         }
 
-        switch (mNavigationMode) {
-        case ActionBar.NAVIGATION_MODE_STANDARD:
-            break;
-        case ActionBar.NAVIGATION_MODE_LIST:
-            if (mListNavLayout != null) {
-                if (showTitle) x += mItemPadding;
-                x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
+        if (mExpandedActionView == null) {
+            final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
+            (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
+            if (showTitle) {
+                x += positionChild(mTitleLayout, x, y, contentHeight);
             }
-            break;
-        case ActionBar.NAVIGATION_MODE_TABS:
-            if (mTabScrollView != null) {
-                if (showTitle) x += mItemPadding;
-                x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
+
+            switch (mNavigationMode) {
+                case ActionBar.NAVIGATION_MODE_STANDARD:
+                    break;
+                case ActionBar.NAVIGATION_MODE_LIST:
+                    if (mListNavLayout != null) {
+                        if (showTitle) x += mItemPadding;
+                        x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
+                    }
+                    break;
+                case ActionBar.NAVIGATION_MODE_TABS:
+                    if (mTabScrollView != null) {
+                        if (showTitle) x += mItemPadding;
+                        x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
+                    }
+                    break;
             }
-            break;
         }
 
         int menuLeft = r - l - getPaddingRight();
@@ -861,13 +894,20 @@
             menuLeft -= mIndeterminateProgressView.getMeasuredWidth();
         }
 
-        if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
-            LayoutParams lp = mCustomNavView.getLayoutParams();
+        View customView = null;
+        if (mExpandedActionView != null) {
+            customView = mExpandedActionView;
+        } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 &&
+                mCustomNavView != null) {
+            customView = mCustomNavView;
+        }
+        if (customView != null) {
+            LayoutParams lp = customView.getLayoutParams();
             final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
                     (ActionBar.LayoutParams) lp : null;
 
             final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY;
-            final int navWidth = mCustomNavView.getMeasuredWidth();
+            final int navWidth = customView.getMeasuredWidth();
 
             int topMargin = 0;
             int bottomMargin = 0;
@@ -907,17 +947,17 @@
                 case Gravity.CENTER_VERTICAL:
                     final int paddedTop = mTop + getPaddingTop();
                     final int paddedBottom = mBottom - getPaddingBottom();
-                    ypos = ((paddedBottom - paddedTop) - mCustomNavView.getMeasuredHeight()) / 2;
+                    ypos = ((paddedBottom - paddedTop) - customView.getMeasuredHeight()) / 2;
                     break;
                 case Gravity.TOP:
                     ypos = getPaddingTop() + topMargin;
                     break;
                 case Gravity.BOTTOM:
-                    ypos = getHeight() - getPaddingBottom() - mCustomNavView.getMeasuredHeight()
+                    ypos = getHeight() - getPaddingBottom() - customView.getMeasuredHeight()
                             - bottomMargin;
                     break;
             }
-            x += positionChild(mCustomNavView, xpos, ypos, contentHeight);
+            x += positionChild(customView, xpos, ypos, contentHeight);
         }
 
         if (mProgressView != null) {
@@ -928,9 +968,83 @@
         }
     }
 
+    @Override
+    public LayoutParams generateLayoutParams(LayoutParams lp) {
+        if (lp == null) {
+            lp = generateDefaultLayoutParams();
+        }
+        return lp;
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState state = new SavedState(superState);
+
+        if (mExpandedMenuPresenter != null && mExpandedMenuPresenter.mCurrentExpandedItem != null) {
+            state.expandedMenuItemId = mExpandedMenuPresenter.mCurrentExpandedItem.getItemId();
+        }
+
+        state.isOverflowOpen = isOverflowMenuShowing();
+
+        return state;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable p) {
+        SavedState state = (SavedState) p;
+
+        super.onRestoreInstanceState(state.getSuperState());
+
+        if (state.expandedMenuItemId != 0 &&
+                mExpandedMenuPresenter != null && mOptionsMenu != null) {
+            final MenuItem item = mOptionsMenu.findItem(state.expandedMenuItemId);
+            if (item != null) {
+                item.expandActionView();
+            }
+        }
+
+        if (state.isOverflowOpen) {
+            postShowOverflowMenu();
+        }
+    }
+
+    static class SavedState extends BaseSavedState {
+        int expandedMenuItemId;
+        boolean isOverflowOpen;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            expandedMenuItemId = in.readInt();
+            isOverflowOpen = in.readInt() != 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(expandedMenuItemId);
+            out.writeInt(isOverflowOpen ? 1 : 0);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
     private static class HomeView extends FrameLayout {
         private View mUpView;
-        private View mIconView;
+        private ImageView mIconView;
 
         public HomeView(Context context) {
             this(context, null);
@@ -940,12 +1054,24 @@
             super(context, attrs);
         }
 
+        public void setUp(boolean isUp) {
+            mUpView.setVisibility(isUp ? VISIBLE : GONE);
+        }
+
+        public void setIcon(Drawable icon) {
+            mIconView.setImageDrawable(icon);
+        }
+
         @Override
         protected void onFinishInflate() {
             mUpView = findViewById(com.android.internal.R.id.up);
             mIconView = (ImageView) findViewById(com.android.internal.R.id.home);
         }
 
+        public int getVerticalIconPadding() {
+            return mIconView.getPaddingTop() + mIconView.getPaddingBottom();
+        }
+
         @Override
         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
             measureChildWithMargins(mUpView, widthMeasureSpec, 0, heightMeasureSpec, 0);
@@ -983,4 +1109,111 @@
             mIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
         }
     }
+
+    private class ExpandedActionViewMenuPresenter implements MenuPresenter {
+        MenuBuilder mMenu;
+        MenuItemImpl mCurrentExpandedItem;
+
+        @Override
+        public void initForMenu(Context context, MenuBuilder menu) {
+            // Clear the expanded action view when menus change.
+            mExpandedActionView = null;
+            if (mCurrentExpandedItem != null) {
+                mCurrentExpandedItem.collapseActionView();
+            }
+            mMenu = menu;
+        }
+
+        @Override
+        public MenuView getMenuView(ViewGroup root) {
+            return null;
+        }
+
+        @Override
+        public void updateMenuView(boolean cleared) {
+            // Make sure the expanded item we have is still there.
+            if (mCurrentExpandedItem != null) {
+                boolean found = false;
+                final int count = mMenu.size();
+                for (int i = 0; i < count; i++) {
+                    final MenuItem item = mMenu.getItem(i);
+                    if (item == mCurrentExpandedItem) {
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (!found) {
+                    // The item we had expanded disappeared. Collapse.
+                    collapseItemActionView(mMenu, mCurrentExpandedItem);
+                }
+            }
+        }
+
+        @Override
+        public void setCallback(Callback cb) {
+        }
+
+        @Override
+        public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+            return false;
+        }
+
+        @Override
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        }
+
+        @Override
+        public boolean flagActionItems() {
+            return false;
+        }
+
+        @Override
+        public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+            mExpandedActionView = item.getActionView();
+            mExpandedHomeLayout.setIcon(item.getIcon());
+            mCurrentExpandedItem = item;
+            if (mExpandedActionView.getParent() != ActionBarView.this) {
+                addView(mExpandedActionView);
+            }
+            if (mExpandedHomeLayout.getParent() != ActionBarView.this) {
+                addView(mExpandedHomeLayout);
+            }
+            mHomeLayout.setVisibility(GONE);
+            mTitleLayout.setVisibility(GONE);
+            if (mTabScrollView != null) mTabScrollView.setVisibility(GONE);
+            if (mSpinner != null) mSpinner.setVisibility(GONE);
+            if (mCustomNavView != null) mCustomNavView.setVisibility(GONE);
+            requestLayout();
+            item.setActionViewExpanded(true);
+            return true;
+        }
+
+        @Override
+        public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+            removeView(mExpandedActionView);
+            removeView(mExpandedHomeLayout);
+            if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) {
+                mHomeLayout.setVisibility(VISIBLE);
+            }
+            if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+                mTitleLayout.setVisibility(VISIBLE);
+            }
+            if (mTabScrollView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
+                mTabScrollView.setVisibility(VISIBLE);
+            }
+            if (mSpinner != null && mNavigationMode == ActionBar.NAVIGATION_MODE_LIST) {
+                mSpinner.setVisibility(VISIBLE);
+            }
+            if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
+                mCustomNavView.setVisibility(VISIBLE);
+            }
+            mExpandedActionView = null;
+            mExpandedHomeLayout.setIcon(null);
+            mCurrentExpandedItem = null;
+            requestLayout();
+            item.setActionViewExpanded(false);
+            return true;
+        }
+    }
 }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index db76211..b3520da 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4482,6 +4482,10 @@
             <!-- When this item is shown as an action in the action bar, show a text
                  label with it even if it has an icon representation. -->
             <flag name="withText" value="4" />
+            <!-- This item's action view collapses to a normal menu
+                 item. When expanded, the action view takes over a
+                 larger segment of its container. -->
+            <flag name="collapseActionView" value="8" />
         </attr>
 
         <!-- An optional layout to be used as an action view.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b8a4443..31924bc 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1852,7 +1852,7 @@
 
     <!-- Do not translate.  WebView User Agent string -->
     <string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>)
-        AppleWebKit/534.20 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.20</string>
+        AppleWebKit/534.24 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.24</string>
     <!-- Do not translate.  WebView User Agent targeted content -->
     <string name="web_user_agent_target_content" translatable="false">"Mobile "</string>
 
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
new file mode 100644
index 0000000..eb63c0d
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkStatsHistory.UID_ALL;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import java.util.Random;
+
+@SmallTest
+public class NetworkStatsHistoryTest extends TestCase {
+    private static final String TAG = "NetworkStatsHistoryTest";
+
+    private static final long TEST_START = 1194220800000L;
+
+    private NetworkStatsHistory stats;
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (stats != null) {
+            assertConsistent(stats);
+        }
+    }
+
+    public void testRecordSingleBucket() throws Exception {
+        final long BUCKET_SIZE = HOUR_IN_MILLIS;
+        stats = buildStats(BUCKET_SIZE);
+
+        // record data into narrow window to get single bucket
+        stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 1024L, 2048L);
+
+        assertEquals(1, stats.bucketCount);
+        assertBucket(stats, 0, 1024L, 2048L);
+    }
+
+    public void testRecordEqualBuckets() throws Exception {
+        final long bucketDuration = HOUR_IN_MILLIS;
+        stats = buildStats(bucketDuration);
+
+        // split equally across two buckets
+        final long recordStart = TEST_START + (bucketDuration / 2);
+        stats.recordData(recordStart, recordStart + bucketDuration, 1024L, 128L);
+
+        assertEquals(2, stats.bucketCount);
+        assertBucket(stats, 0, 512L, 64L);
+        assertBucket(stats, 1, 512L, 64L);
+    }
+
+    public void testRecordTouchingBuckets() throws Exception {
+        final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS;
+        stats = buildStats(BUCKET_SIZE);
+
+        // split almost completely into middle bucket, but with a few minutes
+        // overlap into neighboring buckets. total record is 20 minutes.
+        final long recordStart = (TEST_START + BUCKET_SIZE) - MINUTE_IN_MILLIS;
+        final long recordEnd = (TEST_START + (BUCKET_SIZE * 2)) + (MINUTE_IN_MILLIS * 4);
+        stats.recordData(recordStart, recordEnd, 1000L, 5000L);
+
+        assertEquals(3, stats.bucketCount);
+        // first bucket should have (1/20 of value)
+        assertBucket(stats, 0, 50L, 250L);
+        // second bucket should have (15/20 of value)
+        assertBucket(stats, 1, 750L, 3750L);
+        // final bucket should have (4/20 of value)
+        assertBucket(stats, 2, 200L, 1000L);
+    }
+
+    public void testRecordGapBuckets() throws Exception {
+        final long BUCKET_SIZE = HOUR_IN_MILLIS;
+        stats = buildStats(BUCKET_SIZE);
+
+        // record some data today and next week with large gap
+        final long firstStart = TEST_START;
+        final long lastStart = TEST_START + WEEK_IN_MILLIS;
+        stats.recordData(firstStart, firstStart + SECOND_IN_MILLIS, 128L, 256L);
+        stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS, 64L, 512L);
+
+        // we should have two buckets, far apart from each other
+        assertEquals(2, stats.bucketCount);
+        assertBucket(stats, 0, 128L, 256L);
+        assertBucket(stats, 1, 64L, 512L);
+
+        // now record something in middle, spread across two buckets
+        final long middleStart = TEST_START + DAY_IN_MILLIS;
+        final long middleEnd = middleStart + (HOUR_IN_MILLIS * 2);
+        stats.recordData(middleStart, middleEnd, 2048L, 2048L);
+
+        // now should have four buckets, with new record in middle two buckets
+        assertEquals(4, stats.bucketCount);
+        assertBucket(stats, 0, 128L, 256L);
+        assertBucket(stats, 1, 1024L, 1024L);
+        assertBucket(stats, 2, 1024L, 1024L);
+        assertBucket(stats, 3, 64L, 512L);
+    }
+
+    public void testRecordOverlapBuckets() throws Exception {
+        final long BUCKET_SIZE = HOUR_IN_MILLIS;
+        stats = buildStats(BUCKET_SIZE);
+
+        // record some data in one bucket, and another overlapping buckets
+        stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 256L, 256L);
+        final long midStart = TEST_START + (HOUR_IN_MILLIS / 2);
+        stats.recordData(midStart, midStart + HOUR_IN_MILLIS, 1024L, 1024L);
+
+        // should have two buckets, with some data mixed together
+        assertEquals(2, stats.bucketCount);
+        assertBucket(stats, 0, 768L, 768L);
+        assertBucket(stats, 1, 512L, 512L);
+    }
+
+    public void testRemove() throws Exception {
+        final long BUCKET_SIZE = HOUR_IN_MILLIS;
+        stats = buildStats(BUCKET_SIZE);
+
+        // record some data across 24 buckets
+        stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L);
+        assertEquals(24, stats.bucketCount);
+
+        // try removing far before buckets; should be no change
+        stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
+        assertEquals(24, stats.bucketCount);
+
+        // try removing just moments into first bucket; should be no change
+        // since that bucket contains data beyond the cutoff
+        stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS);
+        assertEquals(24, stats.bucketCount);
+
+        // try removing single bucket
+        stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
+        assertEquals(23, stats.bucketCount);
+
+        // try removing multiple buckets
+        stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
+        assertEquals(20, stats.bucketCount);
+
+        // try removing all buckets
+        stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
+        assertEquals(0, stats.bucketCount);
+    }
+
+    @Suppress
+    public void testFuzzing() throws Exception {
+        try {
+            // fuzzing with random events, looking for crashes
+            final Random r = new Random();
+            for (int i = 0; i < 500; i++) {
+                stats = buildStats(r.nextLong());
+                for (int j = 0; j < 10000; j++) {
+                    if (r.nextBoolean()) {
+                        // add range
+                        final long start = r.nextLong();
+                        final long end = start + r.nextInt();
+                        stats.recordData(start, end, r.nextLong(), r.nextLong());
+                    } else {
+                        // trim something
+                        stats.removeBucketsBefore(r.nextLong());
+                    }
+                }
+                assertConsistent(stats);
+            }
+        } catch (Throwable e) {
+            Log.e(TAG, String.valueOf(stats));
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static NetworkStatsHistory buildStats(long bucketSize) {
+        return new NetworkStatsHistory(TYPE_MOBILE, null, UID_ALL, bucketSize);
+    }
+
+    private static void assertConsistent(NetworkStatsHistory stats) {
+        // verify timestamps are monotonic
+        for (int i = 1; i < stats.bucketCount; i++) {
+            assertTrue(stats.bucketStart[i - 1] < stats.bucketStart[i]);
+        }
+    }
+
+    private static void assertBucket(NetworkStatsHistory stats, int index, long rx, long tx) {
+        assertEquals("unexpected rx", rx, stats.rx[index]);
+        assertEquals("unexpected tx", tx, stats.tx[index]);
+    }
+
+}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 45719c2..23eb9cf 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -47,8 +47,9 @@
                 .addEntry(TEST_IFACE, 100, 1024, 0)
                 .addEntry(TEST_IFACE, 101, 0, 1024).build();
 
-        final NetworkStats result = after.subtract(before);
+        final NetworkStats result = after.subtract(before, true);
 
+        // identical data should result in zero delta
         assertEquals(0, result.rx[0]);
         assertEquals(0, result.tx[0]);
         assertEquals(0, result.rx[1]);
@@ -64,7 +65,7 @@
                 .addEntry(TEST_IFACE, 100, 1025, 2)
                 .addEntry(TEST_IFACE, 101, 3, 1028).build();
 
-        final NetworkStats result = after.subtract(before);
+        final NetworkStats result = after.subtract(before, true);
 
         // expect delta between measurements
         assertEquals(1, result.rx[0]);
@@ -83,7 +84,7 @@
                 .addEntry(TEST_IFACE, 101, 0, 1024)
                 .addEntry(TEST_IFACE, 102, 1024, 1024).build();
 
-        final NetworkStats result = after.subtract(before);
+        final NetworkStats result = after.subtract(before, true);
 
         // its okay to have new rows
         assertEquals(0, result.rx[0]);
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h
index 072329d..20abd51 100644
--- a/include/private/media/AudioTrackShared.h
+++ b/include/private/media/AudioTrackShared.h
@@ -26,7 +26,6 @@
 
 // ----------------------------------------------------------------------------
 
-#define THREAD_PRIORITY_AUDIO_CLIENT (ANDROID_PRIORITY_AUDIO)
 // Maximum cumulated timeout milliseconds before restarting audioflinger thread
 #define MAX_STARTUP_TIMEOUT_MS  3000    // Longer timeout period at startup to cope with A2DP init time
 #define MAX_RUN_TIMEOUT_MS      1000
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index f6c4cc7..a41d7ab 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -315,9 +315,9 @@
             cblk->bufferTimeoutMs = MAX_RUN_TIMEOUT_MS;
             cblk->waitTimeMs = 0;
             if (t != 0) {
-               t->run("ClientRecordThread", THREAD_PRIORITY_AUDIO_CLIENT);
+               t->run("ClientRecordThread", ANDROID_PRIORITY_AUDIO);
             } else {
-                setpriority(PRIO_PROCESS, 0, THREAD_PRIORITY_AUDIO_CLIENT);
+                setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_AUDIO);
             }
         } else {
             mActive = 0;
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index ea44f87..6b4391b 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -342,9 +342,9 @@
         cblk->waitTimeMs = 0;
         android_atomic_and(~CBLK_DISABLED_ON, &cblk->flags);
         if (t != 0) {
-           t->run("AudioTrackThread", THREAD_PRIORITY_AUDIO_CLIENT);
+           t->run("AudioTrackThread", ANDROID_PRIORITY_AUDIO);
         } else {
-            setpriority(PRIO_PROCESS, 0, THREAD_PRIORITY_AUDIO_CLIENT);
+            setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_AUDIO);
         }
 
         LOGV("start %p before lock cblk %p", this, mCblk);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
index 4c66a2d..7eb6d22 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
@@ -112,12 +112,11 @@
 
     private final int NUM_OF_ITERATIONS=20;
 
-    private float calculateTimeTaken(long beginTime, int numIterations)
+    private int calculateTimeTaken(long beginTime, int numIterations)
         throws Exception {
         final long duration2 = SystemClock.uptimeMillis();
         final long durationToCreateMediaItem = (duration2 - beginTime);
-        final float timeTaken1 = (float)durationToCreateMediaItem *
-            1.0f/(float)numIterations;
+        final int timeTaken1 = (int)(durationToCreateMediaItem / numIterations);
         return (timeTaken1);
     }
 
@@ -208,7 +207,7 @@
         final String[] loggingInfo = new String[3];
         final MediaVideoItem[] mediaVideoItem =
             new MediaVideoItem[NUM_OF_ITERATIONS];
-        float timeTaken = 0.0f;
+        int timeTaken = 0;
         long startTime = 0;
 
         /** Time Take for creation of Media Video Item */
@@ -251,7 +250,7 @@
         final String[] loggingInfo = new String[3];
         final MediaImageItem[] mediaImageItem =
             new MediaImageItem[NUM_OF_ITERATIONS];
-        float timeTaken = 0.0f;
+        int timeTaken = 0;
 
         long beginTime = SystemClock.uptimeMillis();
         createImageItems(mediaImageItem, imageItemFileName, renderingMode,
@@ -296,7 +295,7 @@
         final int transitionDuration = 5000;
         final int transitionBehavior = Transition.BEHAVIOR_MIDDLE_FAST;
         final String[] loggingInfo = new String[3];
-        float timeTaken = 0.0f;
+        int timeTaken = 0;
 
         final MediaVideoItem[] mediaVideoItem =
             new MediaVideoItem[(NUM_OF_ITERATIONS *10) + 1];
@@ -514,7 +513,7 @@
         /** 18.Enable Looping for Audio Track.
          * */
         audioTrack.enableLoop();
-        float timeTaken = 0.0f;
+        int timeTaken = 0;
         final long beginTime = SystemClock.uptimeMillis();
             try {
                 mVideoEditor.export(outFilename, outHeight, outBitrate,
@@ -557,7 +556,7 @@
         mediaVideoItem.setExtractBoundaries(videoItemStartTime,
             videoItemEndTime);
 
-        float timeTaken = 0.0f;
+        int timeTaken = 0;
         long beginTime = SystemClock.uptimeMillis();
         for (int i = 0; i < NUM_OF_ITERATIONS; i++) {
             mediaVideoItem.getThumbnail(mediaVideoItem.getWidth() / 2,
@@ -603,7 +602,7 @@
         final OverlayFrame overlayFrame[] = new OverlayFrame[NUM_OF_ITERATIONS];
         final Bitmap mBitmap =  mVideoEditorHelper.getBitmap(overlayFilename,
             640, 480);
-        float timeTaken = 0.0f;
+        int timeTaken = 0;
         long beginTime = SystemClock.uptimeMillis();
         for (int i = 0; i < NUM_OF_ITERATIONS; i++) {
             overlayFrame[i] = new OverlayFrame(mediaVideoItem, "overlay" + i,
@@ -647,7 +646,7 @@
         final int videoProfile = MediaProperties.H264_PROFILE_0_LEVEL_1_3;
         final int width = 1080;
         final int height = MediaProperties.HEIGHT_720;
-        float timeTaken = 0.0f;
+        int timeTaken = 0;
         final String[] loggingInfo = new String[1];
         final MediaVideoItem mediaVideoItem = new MediaVideoItem(mVideoEditor,
             "m0", videoItemFileName1, renderingMode);
@@ -1006,7 +1005,7 @@
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
         final int audioVolume = 50;
         final String[] loggingInfo = new String[2];
-        float timeTaken = 0.0f;
+        int timeTaken = 0;
 
         final MediaVideoItem mediaVideoItem = new MediaVideoItem(mVideoEditor,
             "mediaItem1", videoItemFileName1, renderingMode);
@@ -1057,7 +1056,7 @@
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
         final String[] loggingInfo = new String[3];
 
-        float timeTaken = 0.0f;
+        int timeTaken = 0;
 
         final MediaImageItem[] mediaImageItem =
             new MediaImageItem[NUM_OF_ITERATIONS];
diff --git a/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml b/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
index 109cfff..a4564e6 100644
--- a/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
+++ b/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
@@ -37,6 +37,7 @@
               android:layout_marginBottom="30dp" />
 
     <Button android:id="@+id/button_allow"
+            android:filterTouchesWhenObscured="true"
             android:text="@string/allow_backup_button_label"
             android:layout_below="@id/package_name"
             android:layout_height="wrap_content"
diff --git a/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml b/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
index a1f9a4a..ca99ae1 100644
--- a/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
+++ b/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
@@ -37,6 +37,7 @@
               android:layout_marginBottom="30dp" />
 
     <Button android:id="@+id/button_allow"
+            android:filterTouchesWhenObscured="true"
             android:text="@string/allow_restore_button_label"
             android:layout_below="@id/package_name"
             android:layout_height="wrap_content"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
index e9db998..339e3f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
@@ -422,9 +422,8 @@
             Log.d(TAG, "Get text from: " + imi.getPackageName() + subtype.getNameResId()
                     + imi.getServiceInfo().applicationInfo);
         }
-        // TODO: Change the language of subtype name according to subtype's locale.
-        return mPackageManager.getText(
-                imi.getPackageName(), subtype.getNameResId(), imi.getServiceInfo().applicationInfo);
+        return subtype.getDisplayName(
+                mContext, imi.getPackageName(), imi.getServiceInfo().applicationInfo);
     }
 
     private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index d997109..75f466a 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1446,7 +1446,9 @@
         }
 
         if (mActionBar != null) {
-            outState.putBoolean(ACTION_BAR_TAG, mActionBar.isOverflowMenuShowing());
+            SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
+            mActionBar.saveHierarchyState(actionBarStates);
+            outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
         }
 
         return outState;
@@ -1484,8 +1486,10 @@
             restorePanelState(panelStates);
         }
 
-        if (mActionBar != null && savedInstanceState.getBoolean(ACTION_BAR_TAG)) {
-            mActionBar.postShowOverflowMenu();
+        if (mActionBar != null) {
+            SparseArray<Parcelable> actionBarStates =
+                    savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
+            mActionBar.restoreHierarchyState(actionBarStates);
         }
     }
 
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index a564c2d..dd76eb8 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,6 +16,11 @@
 
 package com.android.server;
 
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+
 import android.bluetooth.BluetoothTetheringDataTracker;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -26,11 +31,13 @@
 import android.net.DummyDataStateTracker;
 import android.net.EthernetDataTracker;
 import android.net.IConnectivityManager;
-import android.net.LinkAddress;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkPolicyManager;
 import android.net.LinkProperties;
 import android.net.MobileDataStateTracker;
 import android.net.NetworkConfig;
 import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkStateTracker;
 import android.net.NetworkUtils;
 import android.net.Proxy;
@@ -54,6 +61,7 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.SparseIntArray;
 
 import com.android.internal.telephony.Phone;
 import com.android.server.connectivity.Tethering;
@@ -62,13 +70,12 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.InetAddress;
-import java.net.Inet4Address;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.GregorianCalendar;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * @hide
@@ -78,6 +85,8 @@
     private static final boolean DBG = true;
     private static final String TAG = "ConnectivityService";
 
+    private static final boolean LOGD_RULES = false;
+
     // how long to wait before switching back to a radio's default network
     private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
     // system property that can override the above value
@@ -91,6 +100,9 @@
     private Tethering mTethering;
     private boolean mTetheringConfigValid = false;
 
+    /** Currently active network rules by UID. */
+    private SparseIntArray mUidRules = new SparseIntArray();
+
     /**
      * Sometimes we want to refer to the individual network state
      * trackers separately, and sometimes we just want to treat them
@@ -128,6 +140,7 @@
     private AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true);
 
     private INetworkManagementService mNetd;
+    private INetworkPolicyManager mPolicyManager;
 
     private static final int ENABLED  = 1;
     private static final int DISABLED = 0;
@@ -250,14 +263,8 @@
     }
     RadioAttributes[] mRadioAttributes;
 
-    public static synchronized ConnectivityService getInstance(Context context) {
-        if (sServiceInstance == null) {
-            sServiceInstance = new ConnectivityService(context);
-        }
-        return sServiceInstance;
-    }
-
-    private ConnectivityService(Context context) {
+    public ConnectivityService(
+            Context context, INetworkManagementService netd, INetworkPolicyManager policyManager) {
         if (DBG) log("ConnectivityService starting up");
 
         HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
@@ -290,9 +297,19 @@
             loge("Error setting defaultDns using " + dns);
         }
 
-        mContext = context;
+        mContext = checkNotNull(context, "missing Context");
+        mNetd = checkNotNull(netd, "missing INetworkManagementService");
+        mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
 
-        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        try {
+            mPolicyManager.registerListener(mPolicyListener);
+        } catch (RemoteException e) {
+            // ouch, no rules updates means some processes may never get network
+            Slog.e(TAG, "unable to register INetworkPolicyListener", e);
+        }
+
+        final PowerManager powerManager = (PowerManager) context.getSystemService(
+                Context.POWER_SERVICE);
         mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_networkTransitionTimeout);
@@ -536,32 +553,92 @@
     }
 
     /**
+     * Check if UID is blocked from using the given {@link NetworkInfo}.
+     */
+    private boolean isNetworkBlocked(NetworkInfo info, int uid) {
+        synchronized (mUidRules) {
+            return isNetworkBlockedLocked(info, uid);
+        }
+    }
+
+    /**
+     * Check if UID is blocked from using the given {@link NetworkInfo}.
+     */
+    private boolean isNetworkBlockedLocked(NetworkInfo info, int uid) {
+        // TODO: expand definition of "paid" network to cover tethered or paid
+        // hotspot use cases.
+        final boolean networkIsPaid = info.getType() != ConnectivityManager.TYPE_WIFI;
+        final int uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+
+        if (networkIsPaid && (uidRules & RULE_REJECT_PAID) != 0) {
+            return true;
+        }
+
+        // no restrictive rules; network is visible
+        return false;
+    }
+
+    /**
      * Return NetworkInfo for the active (i.e., connected) network interface.
      * It is assumed that at most one network is active at a time. If more
      * than one is active, it is indeterminate which will be returned.
      * @return the info for the active network, or {@code null} if none is
      * active
      */
+    @Override
     public NetworkInfo getActiveNetworkInfo() {
-        return getNetworkInfo(mActiveDefaultNetwork);
+        enforceAccessPermission();
+        final int uid = Binder.getCallingUid();
+        return getNetworkInfo(mActiveDefaultNetwork, uid);
     }
 
+    @Override
+    public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+        enforceConnectivityInternalPermission();
+        return getNetworkInfo(mActiveDefaultNetwork, uid);
+    }
+
+    @Override
     public NetworkInfo getNetworkInfo(int networkType) {
         enforceAccessPermission();
-        if (ConnectivityManager.isNetworkTypeValid(networkType)) {
-            NetworkStateTracker t = mNetTrackers[networkType];
-            if (t != null)
-                return t.getNetworkInfo();
-        }
-        return null;
+        final int uid = Binder.getCallingUid();
+        return getNetworkInfo(networkType, uid);
     }
 
+    private NetworkInfo getNetworkInfo(int networkType, int uid) {
+        NetworkInfo info = null;
+        if (isNetworkTypeValid(networkType)) {
+            final NetworkStateTracker tracker = mNetTrackers[networkType];
+            if (tracker != null) {
+                info = tracker.getNetworkInfo();
+                if (isNetworkBlocked(info, uid)) {
+                    // network is blocked; clone and override state
+                    info = new NetworkInfo(info);
+                    info.setDetailedState(DetailedState.BLOCKED, null, null);
+                }
+            }
+        }
+        return info;
+    }
+
+    @Override
     public NetworkInfo[] getAllNetworkInfo() {
         enforceAccessPermission();
-        NetworkInfo[] result = new NetworkInfo[mNetworksDefined];
+        final int uid = Binder.getCallingUid();
+        final NetworkInfo[] result = new NetworkInfo[mNetworksDefined];
         int i = 0;
-        for (NetworkStateTracker t : mNetTrackers) {
-            if(t != null) result[i++] = t.getNetworkInfo();
+        synchronized (mUidRules) {
+            for (NetworkStateTracker tracker : mNetTrackers) {
+                if (tracker != null) {
+                    NetworkInfo info = tracker.getNetworkInfo();
+                    if (isNetworkBlockedLocked(info, uid)) {
+                        // network is blocked; clone and override state
+                        info = new NetworkInfo(info);
+                        info.setDetailedState(DetailedState.BLOCKED, null, null);
+                    }
+                    result[i++] = info;
+                }
+            }
         }
         return result;
     }
@@ -574,15 +651,19 @@
      * @return the ip properties for the active network, or {@code null} if
      * none is active
      */
+    @Override
     public LinkProperties getActiveLinkProperties() {
         return getLinkProperties(mActiveDefaultNetwork);
     }
 
+    @Override
     public LinkProperties getLinkProperties(int networkType) {
         enforceAccessPermission();
-        if (ConnectivityManager.isNetworkTypeValid(networkType)) {
-            NetworkStateTracker t = mNetTrackers[networkType];
-            if (t != null) return t.getLinkProperties();
+        if (isNetworkTypeValid(networkType)) {
+            final NetworkStateTracker tracker = mNetTrackers[networkType];
+            if (tracker != null) {
+                return tracker.getLinkProperties();
+            }
         }
         return null;
     }
@@ -1027,6 +1108,30 @@
         }
     }
 
+    private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+        @Override
+        public void onRulesChanged(int uid, int uidRules) {
+            // only someone like NPMS should only be calling us
+            // TODO: create permission for modifying data policy
+            mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+
+            if (LOGD_RULES) {
+                Slog.d(TAG, "onRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
+            }
+
+            synchronized (mUidRules) {
+                // skip update when we've already applied rules
+                final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+                if (oldRules == uidRules) return;
+
+                mUidRules.put(uid, uidRules);
+            }
+
+            // TODO: dispatch into NMS to push rules towards kernel module
+            // TODO: notify UID when it has requested targeted updates
+        }
+    };
+
     /**
      * @see ConnectivityManager#setMobileDataEnabled(boolean)
      */
@@ -1284,9 +1389,6 @@
     }
 
     void systemReady() {
-        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-        mNetd = INetworkManagementService.Stub.asInterface(b);
-
         synchronized(this) {
             mSystemReady = true;
             if (mInitialBroadcast != null) {
@@ -2255,4 +2357,11 @@
         }
         return networkType;
     }
+
+    private static <T> T checkNotNull(T value, String message) {
+        if (value == null) {
+            throw new NullPointerException(message);
+        }
+        return value;
+    }
 }
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index c96273b..f4308cd 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1885,8 +1885,8 @@
                             int nameResId = subtype.getNameResId();
                             String mode = subtype.getMode();
                             if (nameResId != 0) {
-                                title = TextUtils.concat(pm.getText(imi.getPackageName(),
-                                        nameResId, imi.getServiceInfo().applicationInfo),
+                                title = TextUtils.concat(subtype.getDisplayName(context,
+                                        imi.getPackageName(), imi.getServiceInfo().applicationInfo),
                                         (TextUtils.isEmpty(label) ? "" : " (" + label + ")"));
                             } else {
                                 CharSequence language = subtype.getLocale();
diff --git a/services/java/com/android/server/NetStatService.java b/services/java/com/android/server/NetStatService.java
deleted file mode 100644
index 7fe6743..0000000
--- a/services/java/com/android/server/NetStatService.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.content.Context;
-import android.net.TrafficStats;
-import android.os.INetStatService;
-import android.os.SystemClock;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-public class NetStatService extends INetStatService.Stub {
-    private final Context mContext;
-
-    public NetStatService(Context context) {
-        mContext = context;
-    }
-
-    public long getMobileTxPackets() {
-        return TrafficStats.getMobileTxPackets();
-    }
-
-    public long getMobileRxPackets() {
-        return TrafficStats.getMobileRxPackets();
-    }
-
-    public long getMobileTxBytes() {
-        return TrafficStats.getMobileTxBytes();
-    }
-
-    public long getMobileRxBytes() {
-        return TrafficStats.getMobileRxBytes();
-    }
-
-    public long getTotalTxPackets() {
-        return TrafficStats.getTotalTxPackets();
-    }
-
-    public long getTotalRxPackets() {
-        return TrafficStats.getTotalRxPackets();
-    }
-
-    public long getTotalTxBytes() {
-        return TrafficStats.getTotalTxBytes();
-    }
-
-    public long getTotalRxBytes() {
-        return TrafficStats.getTotalRxBytes();
-    }
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        // This data is accessible to any app -- no permission check needed.
-
-        pw.print("Elapsed: total=");
-        pw.print(SystemClock.elapsedRealtime());
-        pw.print("ms awake=");
-        pw.print(SystemClock.uptimeMillis());
-        pw.println("ms");
-
-        pw.print("Mobile: Tx=");
-        pw.print(getMobileTxBytes());
-        pw.print("B/");
-        pw.print(getMobileTxPackets());
-        pw.print("Pkts Rx=");
-        pw.print(getMobileRxBytes());
-        pw.print("B/");
-        pw.print(getMobileRxPackets());
-        pw.println("Pkts");
-
-        pw.print("Total: Tx=");
-        pw.print(getTotalTxBytes());
-        pw.print("B/");
-        pw.print(getTotalTxPackets());
-        pw.print("Pkts Rx=");
-        pw.print(getTotalRxBytes());
-        pw.print("B/");
-        pw.print(getTotalRxPackets());
-        pw.println("Pkts");
-    }
-}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5355d44..596cbac 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,19 +16,6 @@
 
 package com.android.server;
 
-import com.android.server.accessibility.AccessibilityManagerService;
-import com.android.server.am.ActivityManagerService;
-import com.android.server.net.NetworkPolicyManagerService;
-import com.android.server.pm.PackageManagerService;
-import com.android.server.usb.UsbService;
-import com.android.server.wm.WindowManagerService;
-import com.android.internal.app.ShutdownThread;
-import com.android.internal.os.BinderInternal;
-import com.android.internal.os.SamplingProfilerIntegration;
-
-import dalvik.system.VMRuntime;
-import dalvik.system.Zygote;
-
 import android.accounts.AccountManagerService;
 import android.app.ActivityManagerNative;
 import android.bluetooth.BluetoothAdapter;
@@ -41,25 +28,35 @@
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.media.AudioService;
-import android.os.Build;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.provider.Contacts.People;
 import android.provider.Settings;
 import android.server.BluetoothA2dpService;
 import android.server.BluetoothService;
 import android.server.search.SearchManagerService;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
-import android.util.Log;
 import android.util.Slog;
-import android.view.Display;
 import android.view.WindowManager;
 
+import com.android.internal.app.ShutdownThread;
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.SamplingProfilerIntegration;
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.am.ActivityManagerService;
+import com.android.server.net.NetworkPolicyManagerService;
+import com.android.server.net.NetworkStatsService;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.usb.UsbService;
+import com.android.server.wm.WindowManagerService;
+
+import dalvik.system.VMRuntime;
+import dalvik.system.Zygote;
+
 import java.io.File;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -120,6 +117,9 @@
         LightsService lights = null;
         PowerManagerService power = null;
         BatteryService battery = null;
+        AlarmManagerService alarm = null;
+        NetworkManagementService networkManagement = null;
+        NetworkStatsService networkStats = null;
         NetworkPolicyManagerService networkPolicy = null;
         ConnectivityService connectivity = null;
         IPackageManager pm = null;
@@ -191,7 +191,7 @@
             power.init(context, lights, ActivityManagerService.getDefault(), battery);
 
             Slog.i(TAG, "Alarm Manager");
-            AlarmManagerService alarm = new AlarmManagerService(context);
+            alarm = new AlarmManagerService(context);
             ServiceManager.addService(Context.ALARM_SERVICE, alarm);
 
             Slog.i(TAG, "Init Watchdog");
@@ -277,33 +277,33 @@
             }
 
             try {
-                Slog.i(TAG, "NetStat Service");
-                ServiceManager.addService("netstat", new NetStatService(context));
-            } catch (Throwable e) {
-                Slog.e(TAG, "Failure starting NetStat Service", e);
-            }
-
-            try {
-                Slog.i(TAG, "NetworkPolicy Service");
-                networkPolicy = new NetworkPolicyManagerService(
-                        context, ActivityManagerService.self(), power);
-                ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
-            } catch (Throwable e) {
-                Slog.e(TAG, "Failure starting Connectivity Service", e);
-            }
-
-            try {
                 Slog.i(TAG, "NetworkManagement Service");
-                ServiceManager.addService(
-                        Context.NETWORKMANAGEMENT_SERVICE,
-                        NetworkManagementService.create(context));
+                networkManagement = NetworkManagementService.create(context);
+                ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
             } catch (Throwable e) {
                 Slog.e(TAG, "Failure starting NetworkManagement Service", e);
             }
 
             try {
+                Slog.i(TAG, "NetworkStats Service");
+                networkStats = new NetworkStatsService(context, networkManagement, alarm);
+                ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting NetworkStats Service", e);
+            }
+
+            try {
+                Slog.i(TAG, "NetworkPolicy Service");
+                networkPolicy = new NetworkPolicyManagerService(
+                        context, ActivityManagerService.self(), power, networkStats);
+                ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting NetworkPolicy Service", e);
+            }
+
+            try {
                 Slog.i(TAG, "Connectivity Service");
-                connectivity = ConnectivityService.getInstance(context);
+                connectivity = new ConnectivityService(context, networkManagement, networkPolicy);
                 ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
             } catch (Throwable e) {
                 Slog.e(TAG, "Failure starting Connectivity Service", e);
@@ -539,6 +539,7 @@
         // These are needed to propagate to the runnable below.
         final Context contextF = context;
         final BatteryService batteryF = battery;
+        final NetworkStatsService networkStatsF = networkStats;
         final NetworkPolicyManagerService networkPolicyF = networkPolicy;
         final ConnectivityService connectivityF = connectivity;
         final DockObserver dockF = dock;
@@ -565,6 +566,7 @@
 
                 startSystemUi(contextF);
                 if (batteryF != null) batteryF.systemReady();
+                if (networkStatsF != null) networkStatsF.systemReady();
                 if (networkPolicyF != null) networkPolicyF.systemReady();
                 if (connectivityF != null) connectivityF.systemReady();
                 if (dockF != null) dockF.systemReady();
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index cc9e532..7801aec 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -41,7 +41,6 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -59,7 +58,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -107,16 +105,18 @@
 
     private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
 
-    private final SparseArray<List<AccessibilityServiceInfo>> mFeedbackTypeToEnabledServicesMap =
-        new SparseArray<List<AccessibilityServiceInfo>>();
-
     private PackageManager mPackageManager;
 
     private int mHandledFeedbackTypes = 0;
 
     private boolean mIsEnabled;
+
     private AccessibilityInputFilter mInputFilter;
 
+    private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = new ArrayList<AccessibilityServiceInfo>();
+
+    private boolean mHasInputFilter;
+
     /**
      * Handler for delayed event dispatch.
      */
@@ -259,6 +259,7 @@
                             unbindAllServicesLocked();
                         }
                         updateClientsLocked();
+                        updateInputFilterLocked();
                     }
                 }
             });
@@ -278,9 +279,19 @@
             });
     }
 
-    public boolean addClient(IAccessibilityManagerClient client) {
+    public boolean addClient(IAccessibilityManagerClient client) throws RemoteException {
         synchronized (mLock) {
-            mClients.add(client);
+            final IAccessibilityManagerClient addedClient = client;
+            mClients.add(addedClient);
+            // Clients are registered all the time until their process is
+            // killed, therefore we do not have a corresponding unlinkToDeath.
+            client.asBinder().linkToDeath(new DeathRecipient() {
+                public void binderDied() {
+                    synchronized (mLock) {
+                        mClients.remove(addedClient);
+                    }
+                }
+            }, 0);
             return mIsEnabled;
         }
     }
@@ -307,21 +318,22 @@
     }
 
     public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
-        SparseArray<List<AccessibilityServiceInfo>> feedbackTypeToEnabledServicesMap =
-            mFeedbackTypeToEnabledServicesMap;
-        List<AccessibilityServiceInfo> enabledServices = new ArrayList<AccessibilityServiceInfo>();
+        List<AccessibilityServiceInfo> result = mEnabledServicesForFeedbackTempList;
+        List<Service> services = mServices;
         synchronized (mLock) {
             while (feedbackType != 0) {
                 final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType));
                 feedbackType &= ~feedbackTypeBit;
-                List<AccessibilityServiceInfo> perFeedbackType =
-                    feedbackTypeToEnabledServicesMap.get(feedbackTypeBit);
-                if (perFeedbackType != null) {
-                    enabledServices.addAll(perFeedbackType);
+                final int serviceCount = services.size();
+                for (int i = 0; i < serviceCount; i++) {
+                    Service service = services.get(i);
+                    if (service.mFeedbackType == feedbackType) {
+                        result.add(service.mAccessibilityServiceInfo);
+                    }
                 }
             }
         }
-        return enabledServices;
+        return result;
     }
 
     public void interrupt() {
@@ -331,16 +343,8 @@
                 try {
                     service.mServiceInterface.onInterrupt();
                 } catch (RemoteException re) {
-                    if (re instanceof DeadObjectException) {
-                        Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
-                        if (removeDeadServiceLocked(service)) {
-                            count--;
-                            i--;
-                        }
-                    } else {
-                        Slog.e(LOG_TAG, "Error during sending interrupt request to "
-                                + service.mService, re);
-                    }
+                    Slog.e(LOG_TAG, "Error during sending interrupt request to "
+                        + service.mService, re);
                 }
             }
         }
@@ -364,8 +368,10 @@
                         service.setAccessibilityServiceInfo(oldInfo);
                     } else {
                         service.setAccessibilityServiceInfo(info);
+                        tryAddServiceLocked(service);
                     }
-                    updateStateOnEnabledService(service);
+
+                    updateInputFilterLocked();
                 }
                 return;
             default:
@@ -490,28 +496,45 @@
                 Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
             }
         } catch (RemoteException re) {
-            if (re instanceof DeadObjectException) {
-                Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
-                removeDeadServiceLocked(service);
-            } else {
-                Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
-            }
+            Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
         }
     }
 
     /**
-     * Removes a dead service.
+     * Adds a service.
+     *
+     * @param service The service to add.
+     */
+    private void tryAddServiceLocked(Service service) {
+        try {
+            if (mServices.contains(service) || !service.isConfigured()) {
+                return;
+            }
+            service.linkToOwnDeath();
+            mServices.add(service);
+            mComponentNameToServiceMap.put(service.mComponentName, service);
+            updateInputFilterLocked();
+        } catch (RemoteException e) {
+            /* do nothing */
+        }
+    }
+
+    /**
+     * Removes a service.
      *
      * @param service The service.
      * @return True if the service was removed, false otherwise.
      */
-    private boolean removeDeadServiceLocked(Service service) {
-        if (DEBUG) {
-            Slog.i(LOG_TAG, "Dead service " + service.mService + " removed");
+    private boolean tryRemoveServiceLocked(Service service) {
+        final boolean removed = mServices.remove(service);
+        if (!removed) {
+            return false;
         }
+        mComponentNameToServiceMap.remove(service.mComponentName);
         mHandler.removeMessages(service.mId);
-        updateStateOnDisabledService(service);
-        return mServices.remove(service);
+        service.unlinkToOwnDeath();
+        updateInputFilterLocked();
+        return removed;
     }
 
     /**
@@ -534,11 +557,6 @@
             return false;
         }
 
-        if (!service.mService.isBinderAlive()) {
-            removeDeadServiceLocked(service);
-            return false;
-        }
-
         int eventType = event.getEventType();
         if ((service.mEventTypes & eventType) != eventType) {
             return false;
@@ -663,65 +681,36 @@
     }
 
     /**
-     * Sets the input filter state. If the filter is in enabled it is registered
-     * in the window manager, otherwise the filter is removed from the latter.
-     *
-     * @param enabled Whether the input filter is enabled.
+     * Updates the input filter state. The filter is enabled if accessibility
+     * is enabled and there is at least one accessibility service providing
+     * spoken feedback.
      */
-    private void setInputFilterEnabledLocked(boolean enabled) {
+    private void updateInputFilterLocked() {
         WindowManagerService wm = (WindowManagerService)ServiceManager.getService(
                 Context.WINDOW_SERVICE);
-        if (wm != null) {
-            if (enabled) {
+        if (mIsEnabled) {
+            final boolean hasSpokenFeedbackServices = !getEnabledAccessibilityServiceList(
+                    AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty();
+            if (hasSpokenFeedbackServices) {
+                if (mHasInputFilter) {
+                    return;
+                }
                 if (mInputFilter == null) {
                     mInputFilter = new AccessibilityInputFilter(mContext);
                 }
                 wm.setInputFilter(mInputFilter);
+                mHasInputFilter = true;
             } else {
-                wm.setInputFilter(null);
+                if (mHasInputFilter) {
+                    wm.setInputFilter(null);
+                    mHasInputFilter = false;
+                }
             }
-        }
-    }
-
-    /**
-     * Updates the set of enabled services for a given feedback type and
-     * if more than one of them provides spoken feedback enables touch
-     * exploration.
-     *
-     * @param service An enable service.
-     */
-    private void updateStateOnEnabledService(Service service) {
-        int feedbackType = service.mFeedbackType;
-        List<AccessibilityServiceInfo> enabledServices =
-            mFeedbackTypeToEnabledServicesMap.get(feedbackType);
-        if (enabledServices == null) {
-            enabledServices = new ArrayList<AccessibilityServiceInfo>();
-            mFeedbackTypeToEnabledServicesMap.put(feedbackType, enabledServices);
-        }
-        enabledServices.add(service.mAccessibilityServiceInfo);
-
-        // We enable touch exploration if at least one
-        // enabled service provides spoken feedback.
-        if (enabledServices.size() > 0
-                && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
-            updateClientsLocked();
-            setInputFilterEnabledLocked(true);
-        }
-    }
-
-    private void updateStateOnDisabledService(Service service) {
-        List<AccessibilityServiceInfo> enabledServices =
-            mFeedbackTypeToEnabledServicesMap.get(service.mFeedbackType);
-        if (enabledServices == null) {
-            return;
-        }
-        enabledServices.remove(service.mAccessibilityServiceInfo);
-        // We disable touch exploration if no
-        // enabled service provides spoken feedback.
-        if (enabledServices.isEmpty()
-                && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
-            updateClientsLocked();
-            setInputFilterEnabledLocked(false);
+        } else {
+            if (mHasInputFilter) {
+                wm.setInputFilter(null);
+                mHasInputFilter = false;
+            }
         }
     }
 
@@ -733,7 +722,8 @@
      * passed to the service it represents as soon it is bound. It also serves as the
      * connection for the service.
      */
-    class Service extends IAccessibilityServiceConnection.Stub implements ServiceConnection {
+    class Service extends IAccessibilityServiceConnection.Stub
+            implements ServiceConnection, DeathRecipient {
         int mId = 0;
 
         AccessibilityServiceInfo mAccessibilityServiceInfo;
@@ -752,8 +742,6 @@
 
         long mNotificationTimeout;
 
-        boolean mIsActive;
-
         ComponentName mComponentName;
 
         Intent mIntent;
@@ -806,11 +794,9 @@
          */
         public boolean unbind() {
             if (mService != null) {
-                mService = null;
+                tryRemoveServiceLocked(this);
                 mContext.unbindService(this);
-                mComponentNameToServiceMap.remove(mComponentName);
-                mServices.remove(this);
-                updateStateOnDisabledService(this);
+                mService = null;
                 return true;
             }
             return false;
@@ -833,17 +819,10 @@
         public void onServiceConnected(ComponentName componentName, IBinder service) {
             mService = service;
             mServiceInterface = IEventListener.Stub.asInterface(service);
-
             try {
                 mServiceInterface.setConnection(this);
                 synchronized (mLock) {
-                    if (!mServices.contains(this)) {
-                        mServices.add(this);
-                        mComponentNameToServiceMap.put(componentName, this);
-                        if (isConfigured()) {
-                            updateStateOnEnabledService(this);
-                        }
-                    }
+                    tryAddServiceLocked(this);
                 }
             } catch (RemoteException re) {
                 Slog.w(LOG_TAG, "Error while setting Controller for service: " + service, re);
@@ -851,9 +830,20 @@
         }
 
         public void onServiceDisconnected(ComponentName componentName) {
+            /* do nothing - #binderDied takes care */
+        }
+
+        public void linkToOwnDeath() throws RemoteException {
+            mService.linkToDeath(this, 0);
+        }
+
+        public void unlinkToOwnDeath() {
+            mService.unlinkToDeath(this, 0);
+        }
+
+        public void binderDied() {
             synchronized (mLock) {
-                Service service = mComponentNameToServiceMap.remove(componentName);
-                mServices.remove(service);
+                tryRemoveServiceLocked(this);
             }
         }
     }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index b463e56..5f52056 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -25,7 +25,6 @@
 import com.android.server.SystemServer;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityStack.ActivityState;
-import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.wm.WindowManagerService;
 
 import dalvik.system.Zygote;
@@ -1301,15 +1300,16 @@
                 break;
             }
             case DISPATCH_FOREGROUND_ACTIVITIES_CHANGED: {
-                final ProcessRecord app = (ProcessRecord) msg.obj;
-                final boolean foregroundActivities = msg.arg1 != 0;
-                dispatchForegroundActivitiesChanged(
-                        app.pid, app.info.uid, foregroundActivities);
+                final int pid = msg.arg1;
+                final int uid = msg.arg2;
+                final boolean foregroundActivities = (Boolean) msg.obj;
+                dispatchForegroundActivitiesChanged(pid, uid, foregroundActivities);
                 break;
             }
             case DISPATCH_PROCESS_DIED: {
-                final ProcessRecord app = (ProcessRecord) msg.obj;
-                dispatchProcessDied(app.pid, app.info.uid);
+                final int pid = msg.arg1;
+                final int uid = msg.arg2;
+                dispatchProcessDied(pid, uid);
                 break;
             }
             }
@@ -9260,7 +9260,7 @@
             }
         }
 
-        mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app).sendToTarget();
+        mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();
 
         // If the caller is restarting this app, then leave it in its
         // current lists and let the caller take care of it.
@@ -12821,8 +12821,8 @@
         app.curSchedGroup = schedGroup;
 
         if (hadForegroundActivities != app.foregroundActivities) {
-            mHandler.obtainMessage(DISPATCH_FOREGROUND_ACTIVITIES_CHANGED,
-                    app.foregroundActivities ? 1 : 0, 0, app).sendToTarget();
+            mHandler.obtainMessage(DISPATCH_FOREGROUND_ACTIVITIES_CHANGED, app.pid, app.info.uid,
+                    app.foregroundActivities).sendToTarget();
         }
 
         return adj;
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index d083d01..17c7161 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -16,11 +16,15 @@
 
 package com.android.server.net;
 
+import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.MANAGE_APP_TOKENS;
 import static android.Manifest.permission.UPDATE_DEVICE_STATS;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_BACKGROUND;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+import static android.net.NetworkPolicyManager.dumpPolicy;
+import static android.net.NetworkPolicyManager.dumpRules;
 
 import android.app.IActivityManager;
 import android.app.IProcessObserver;
@@ -28,51 +32,73 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
 import android.os.IPowerManager;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 /**
  * Service that maintains low-level network policy rules and collects usage
  * statistics to drive those rules.
+ * <p>
+ * Derives active rules by combining a given policy with other system status,
+ * and delivers to listeners, such as {@link ConnectivityManager}, for
+ * enforcement.
  */
 public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
     private static final String TAG = "NetworkPolicy";
     private static final boolean LOGD = true;
 
-    private Context mContext;
-    private IActivityManager mActivityManager;
-    private IPowerManager mPowerManager;
+    private final Context mContext;
+    private final IActivityManager mActivityManager;
+    private final IPowerManager mPowerManager;
+    private final INetworkStatsService mNetworkStats;
 
-    private Object mRulesLock = new Object();
+    private final Object mRulesLock = new Object();
 
-    private boolean mScreenOn = false;
+    private boolean mScreenOn;
 
     /** Current network policy for each UID. */
     private SparseIntArray mUidPolicy = new SparseIntArray();
+    /** Current derived network rules for each UID. */
+    private SparseIntArray mUidRules = new SparseIntArray();
 
     /** Foreground at both UID and PID granularity. */
     private SparseBooleanArray mUidForeground = new SparseBooleanArray();
     private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
             SparseBooleanArray>();
 
-    // TODO: periodically poll network stats and write to disk
+    private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
+            INetworkPolicyListener>();
+
     // TODO: save/restore policy information from disk
 
-    public NetworkPolicyManagerService(
-            Context context, IActivityManager activityManager, IPowerManager powerManager) {
+    // TODO: keep whitelist of system-critical services that should never have
+    // rules enforced, such as system, phone, and radio UIDs.
+
+    // TODO: keep record of billing cycle details, and limit rules
+    // TODO: keep map of interfaces-to-billing-relationship
+
+    public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
+            IPowerManager powerManager, INetworkStatsService networkStats) {
         mContext = checkNotNull(context, "missing context");
         mActivityManager = checkNotNull(activityManager, "missing activityManager");
         mPowerManager = checkNotNull(powerManager, "missing powerManager");
+        mNetworkStats = checkNotNull(networkStats, "missing networkStats");
     }
 
     public void systemReady() {
-        // TODO: read current policy+stats from disk and generate NMS rules
+        // TODO: read current policy from disk
 
         updateScreenOn();
 
@@ -92,18 +118,13 @@
         screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
         mContext.registerReceiver(mScreenReceiver, screenFilter);
 
-        final IntentFilter shutdownFilter = new IntentFilter();
-        shutdownFilter.addAction(Intent.ACTION_SHUTDOWN);
-        mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
-
     }
 
     private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
         @Override
         public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
             // only someone like AMS should only be calling us
-            mContext.enforceCallingOrSelfPermission(
-                    MANAGE_APP_TOKENS, "requires MANAGE_APP_TOKENS permission");
+            mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
 
             synchronized (mRulesLock) {
                 // because a uid can have multiple pids running inside, we need to
@@ -123,8 +144,7 @@
         @Override
         public void onProcessDied(int pid, int uid) {
             // only someone like AMS should only be calling us
-            mContext.enforceCallingOrSelfPermission(
-                    MANAGE_APP_TOKENS, "requires MANAGE_APP_TOKENS permission");
+            mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
 
             synchronized (mRulesLock) {
                 // clear records and recompute, when they exist
@@ -148,22 +168,19 @@
         }
     };
 
-    private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // TODO: persist any pending stats during clean shutdown
-            Log.d(TAG, "persisting stats");
-        }
-    };
-
     @Override
     public void setUidPolicy(int uid, int policy) {
-        mContext.enforceCallingOrSelfPermission(
-                UPDATE_DEVICE_STATS, "requires UPDATE_DEVICE_STATS permission");
+        // TODO: create permission for modifying data policy
+        mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
 
+        final int oldPolicy;
         synchronized (mRulesLock) {
+            oldPolicy = getUidPolicy(uid);
             mUidPolicy.put(uid, policy);
+            updateRulesForUidL(uid);
         }
+
+        // TODO: consider dispatching BACKGROUND_DATA_SETTING broadcast
     }
 
     @Override
@@ -173,6 +190,86 @@
         }
     }
 
+    @Override
+    public void registerListener(INetworkPolicyListener listener) {
+        mListeners.register(listener);
+
+        synchronized (mRulesLock) {
+            // dispatch any existing rules to new listeners
+            final int size = mUidRules.size();
+            for (int i = 0; i < size; i++) {
+                final int uid = mUidRules.keyAt(i);
+                final int uidRules = mUidRules.valueAt(i);
+                if (uidRules != RULE_ALLOW_ALL) {
+                    try {
+                        listener.onRulesChanged(uid, uidRules);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void unregisterListener(INetworkPolicyListener listener) {
+        mListeners.unregister(listener);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+        synchronized (mRulesLock) {
+            fout.println("Policy status for known UIDs:");
+
+            final SparseBooleanArray knownUids = new SparseBooleanArray();
+            collectKeys(mUidPolicy, knownUids);
+            collectKeys(mUidForeground, knownUids);
+            collectKeys(mUidRules, knownUids);
+
+            final int size = knownUids.size();
+            for (int i = 0; i < size; i++) {
+                final int uid = knownUids.keyAt(i);
+                fout.print("  UID=");
+                fout.print(uid);
+
+                fout.print(" policy=");
+                final int policyIndex = mUidPolicy.indexOfKey(uid);
+                if (policyIndex < 0) {
+                    fout.print("UNKNOWN");
+                } else {
+                    dumpPolicy(fout, mUidPolicy.valueAt(policyIndex));
+                }
+
+                fout.print(" foreground=");
+                final int foregroundIndex = mUidPidForeground.indexOfKey(uid);
+                if (foregroundIndex < 0) {
+                    fout.print("UNKNOWN");
+                } else {
+                    dumpSparseBooleanArray(fout, mUidPidForeground.valueAt(foregroundIndex));
+                }
+
+                fout.print(" rules=");
+                final int rulesIndex = mUidRules.indexOfKey(uid);
+                if (rulesIndex < 0) {
+                    fout.print("UNKNOWN");
+                } else {
+                    dumpRules(fout, mUidRules.valueAt(rulesIndex));
+                }
+
+                fout.println();
+            }
+        }
+    }
+
+    @Override
+    public boolean isUidForeground(int uid) {
+        synchronized (mRulesLock) {
+            // only really in foreground when screen is also on
+            return mUidForeground.get(uid, false) && mScreenOn;
+        }
+    }
+
     /**
      * Foreground for PID changed; recompute foreground at UID level. If
      * changed, will trigger {@link #updateRulesForUidL(int)}.
@@ -223,22 +320,33 @@
     }
 
     private void updateRulesForUidL(int uid) {
-        // only really in foreground when screen on
-        final boolean uidForeground = mUidForeground.get(uid, false) && mScreenOn;
         final int uidPolicy = getUidPolicy(uid);
+        final boolean uidForeground = isUidForeground(uid);
 
-        if (LOGD) {
-            Log.d(TAG, "updateRulesForUid(uid=" + uid + ") found foreground=" + uidForeground
-                    + " and policy=" + uidPolicy);
+        // derive active rules based on policy and active state
+        int uidRules = RULE_ALLOW_ALL;
+        if (!uidForeground && (uidPolicy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
+            // uid in background, and policy says to block paid data
+            uidRules = RULE_REJECT_PAID;
         }
 
-        if (!uidForeground && (uidPolicy & POLICY_REJECT_BACKGROUND) != 0) {
-            // TODO: build updated rules and push to NMS
-        } else if ((uidPolicy & POLICY_REJECT_PAID) != 0) {
-            // TODO: build updated rules and push to NMS
-        } else {
-            // TODO: build updated rules and push to NMS
+        // TODO: only dispatch when rules actually change
+
+        // record rule locally to dispatch to new listeners
+        mUidRules.put(uid, uidRules);
+
+        // dispatch changed rule to existing listeners
+        final int length = mListeners.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+            if (listener != null) {
+                try {
+                    listener.onRulesChanged(uid, uidRules);
+                } catch (RemoteException e) {
+                }
+            }
         }
+        mListeners.finishBroadcast();
     }
 
     private static <T> T checkNotNull(T value, String message) {
@@ -247,4 +355,28 @@
         }
         return value;
     }
+
+    private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
+        final int size = source.size();
+        for (int i = 0; i < size; i++) {
+            target.put(source.keyAt(i), true);
+        }
+    }
+
+    private static void collectKeys(SparseBooleanArray source, SparseBooleanArray target) {
+        final int size = source.size();
+        for (int i = 0; i < size; i++) {
+            target.put(source.keyAt(i), true);
+        }
+    }
+
+    private static void dumpSparseBooleanArray(PrintWriter fout, SparseBooleanArray value) {
+        fout.print("[");
+        final int size = value.size();
+        for (int i = 0; i < size; i++) {
+            fout.print(value.keyAt(i) + "=" + value.valueAt(i));
+            if (i < size - 1) fout.print(",");
+        }
+        fout.print("]");
+    }
 }
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
new file mode 100644
index 0000000..d9c1f25
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.SHUTDOWN;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkStats.UID_ALL;
+
+import android.app.AlarmManager;
+import android.app.IAlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.INetworkStatsService;
+import android.net.LinkProperties;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.telephony.TelephonyManager;
+import android.text.format.DateUtils;
+import android.util.NtpTrustedTime;
+import android.util.Slog;
+import android.util.TrustedTime;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyIntents;
+import com.google.android.collect.Maps;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+/**
+ * Collect and persist detailed network statistics, and provide this data to
+ * other system services.
+ */
+public class NetworkStatsService extends INetworkStatsService.Stub {
+    private static final String TAG = "NetworkStatsService";
+    private static final boolean LOGD = true;
+
+    private final Context mContext;
+    private final INetworkManagementService mNetworkManager;
+    private final IAlarmManager mAlarmManager;
+    private final TrustedTime mTime;
+
+    private static final String ACTION_NETWORK_STATS_POLL =
+            "com.android.server.action.NETWORK_STATS_POLL";
+
+    private PendingIntent mPollIntent;
+
+    // TODO: move tweakable params to Settings.Secure
+    // TODO: listen for kernel push events through netd instead of polling
+
+    private static final long KB_IN_BYTES = 1024;
+
+    private static final long POLL_INTERVAL = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
+    private static final long SUMMARY_BUCKET_DURATION = 6 * DateUtils.HOUR_IN_MILLIS;
+    private static final long SUMMARY_MAX_HISTORY = 90 * DateUtils.DAY_IN_MILLIS;
+
+    // TODO: remove these high-frequency testing values
+//    private static final long POLL_INTERVAL = 5 * DateUtils.SECOND_IN_MILLIS;
+//    private static final long SUMMARY_BUCKET_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
+//    private static final long SUMMARY_MAX_HISTORY = 2 * DateUtils.MINUTE_IN_MILLIS;
+
+    /** Minimum delta required to persist to disk. */
+    private static final long SUMMARY_PERSIST_THRESHOLD = 64 * KB_IN_BYTES;
+
+    private static final long TIME_CACHE_MAX_AGE = DateUtils.DAY_IN_MILLIS;
+
+    private final Object mStatsLock = new Object();
+
+    /** Set of active ifaces during this boot. */
+    private HashMap<String, InterfaceInfo> mActiveIface = Maps.newHashMap();
+    /** Set of historical stats for known ifaces. */
+    private HashMap<InterfaceInfo, NetworkStatsHistory> mIfaceStats = Maps.newHashMap();
+
+    private NetworkStats mLastPollStats;
+    private NetworkStats mLastPersistStats;
+
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+
+    // TODO: collect detailed uid stats, storing tag-granularity data until next
+    // dropbox, and uid summary for a specific bucket count.
+
+    // TODO: periodically compile statistics and send to dropbox.
+
+    public NetworkStatsService(
+            Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
+        // TODO: move to using cached NtpTrustedTime
+        this(context, networkManager, alarmManager, new NtpTrustedTime());
+    }
+
+    public NetworkStatsService(Context context, INetworkManagementService networkManager,
+            IAlarmManager alarmManager, TrustedTime time) {
+        mContext = checkNotNull(context, "missing Context");
+        mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
+        mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager");
+        mTime = checkNotNull(time, "missing TrustedTime");
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+    }
+
+    public void systemReady() {
+        // read historical stats from disk
+        readStatsLocked();
+
+        // watch other system services that claim interfaces
+        // TODO: protect incoming broadcast with permissions check.
+        // TODO: consider migrating this to ConnectivityService, but it might
+        // cause a circular dependency.
+        final IntentFilter interfaceFilter = new IntentFilter();
+        interfaceFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+        interfaceFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        mContext.registerReceiver(mInterfaceReceiver, interfaceFilter);
+
+        // listen for periodic polling events
+        final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
+        mContext.registerReceiver(mPollReceiver, pollFilter, UPDATE_DEVICE_STATS, mHandler);
+
+        // persist stats during clean shutdown
+        final IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+        mContext.registerReceiver(mShutdownReceiver, shutdownFilter, SHUTDOWN, null);
+
+        try {
+            registerPollAlarmLocked();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "unable to register poll alarm");
+        }
+    }
+
+    /**
+     * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
+     * reschedule based on current {@link #POLL_INTERVAL} value.
+     */
+    private void registerPollAlarmLocked() throws RemoteException {
+        if (mPollIntent != null) {
+            mAlarmManager.remove(mPollIntent);
+        }
+
+        mPollIntent = PendingIntent.getBroadcast(
+                mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
+
+        final long currentRealtime = SystemClock.elapsedRealtime();
+        mAlarmManager.setInexactRepeating(
+                AlarmManager.ELAPSED_REALTIME, currentRealtime, POLL_INTERVAL, mPollIntent);
+    }
+
+    @Override
+    public NetworkStatsHistory[] getNetworkStatsSummary(int networkType) {
+        // TODO: return history for requested types
+        return null;
+    }
+
+    @Override
+    public NetworkStatsHistory getNetworkStatsUid(int uid) {
+        // TODO: return history for requested uid
+        return null;
+    }
+
+    /**
+     * Receiver that watches for other system components that claim network
+     * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
+     * with mobile interfaces.
+     */
+    private BroadcastReceiver mInterfaceReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED.equals(action)) {
+                final LinkProperties prop = intent.getParcelableExtra(
+                        Phone.DATA_LINK_PROPERTIES_KEY);
+                final String iface = prop != null ? prop.getInterfaceName() : null;
+                if (iface != null) {
+                    final TelephonyManager teleManager = (TelephonyManager) context
+                            .getSystemService(Context.TELEPHONY_SERVICE);
+                    final InterfaceInfo info = new InterfaceInfo(
+                            iface, TYPE_MOBILE, teleManager.getSubscriberId());
+                    reportActiveInterface(info);
+                }
+            } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+                final LinkProperties prop = intent.getParcelableExtra(
+                        WifiManager.EXTRA_LINK_PROPERTIES);
+                final String iface = prop != null ? prop.getInterfaceName() : null;
+                if (iface != null) {
+                    final InterfaceInfo info = new InterfaceInfo(iface, TYPE_WIFI, null);
+                    reportActiveInterface(info);
+                }
+            }
+        }
+    };
+
+    private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // already running on background handler, network/io is safe, and
+            // caller verified to have UPDATE_DEVICE_STATS permission above.
+            synchronized (mStatsLock) {
+                // TODO: acquire wakelock while performing poll
+                performPollLocked();
+            }
+        }
+    };
+
+    private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // persist stats during clean shutdown
+            synchronized (mStatsLock) {
+                writeStatsLocked();
+            }
+        }
+    };
+
+    private void performPollLocked() {
+        if (LOGD) Slog.v(TAG, "performPollLocked()");
+
+        // try refreshing time source when stale
+        if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
+            mTime.forceRefresh();
+        }
+
+        // TODO: consider marking "untrusted" times in historical stats
+        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+                : System.currentTimeMillis();
+
+        final NetworkStats current;
+        try {
+            current = mNetworkManager.getNetworkStatsSummary();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "problem reading network stats");
+            return;
+        }
+
+        // update historical usage with delta since last poll
+        final NetworkStats pollDelta = computeStatsDelta(mLastPollStats, current);
+        final long timeStart = currentTime - pollDelta.elapsedRealtime;
+        for (String iface : pollDelta.getKnownIfaces()) {
+            final InterfaceInfo info = mActiveIface.get(iface);
+            if (info == null) {
+                if (LOGD) Slog.w(TAG, "unknown interface " + iface + ", ignoring stats");
+                continue;
+            }
+
+            final int index = pollDelta.findIndex(iface, UID_ALL);
+            final long rx = pollDelta.rx[index];
+            final long tx = pollDelta.tx[index];
+
+            final NetworkStatsHistory history = findOrCreateHistoryLocked(info);
+            history.recordData(timeStart, currentTime, rx, tx);
+            history.removeBucketsBefore(currentTime - SUMMARY_MAX_HISTORY);
+        }
+
+        mLastPollStats = current;
+
+        // decide if enough has changed to trigger persist
+        final NetworkStats persistDelta = computeStatsDelta(mLastPersistStats, current);
+        for (String iface : persistDelta.getKnownIfaces()) {
+            final int index = persistDelta.findIndex(iface, UID_ALL);
+            if (persistDelta.rx[index] > SUMMARY_PERSIST_THRESHOLD
+                    || persistDelta.tx[index] > SUMMARY_PERSIST_THRESHOLD) {
+                writeStatsLocked();
+                mLastPersistStats = current;
+                break;
+            }
+        }
+    }
+
+    private NetworkStatsHistory findOrCreateHistoryLocked(InterfaceInfo info) {
+        NetworkStatsHistory stats = mIfaceStats.get(info);
+        if (stats == null) {
+            stats = new NetworkStatsHistory(
+                    info.networkType, info.identity, UID_ALL, SUMMARY_BUCKET_DURATION);
+            mIfaceStats.put(info, stats);
+        }
+        return stats;
+    }
+
+    private void readStatsLocked() {
+        if (LOGD) Slog.v(TAG, "readStatsLocked()");
+        // TODO: read historical stats from disk using AtomicFile
+    }
+
+    private void writeStatsLocked() {
+        if (LOGD) Slog.v(TAG, "writeStatsLocked()");
+        // TODO: persist historical stats to disk using AtomicFile
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+        pw.println("Active interfaces:");
+        for (InterfaceInfo info : mActiveIface.values()) {
+            info.dump("  ", pw);
+        }
+
+        pw.println("Known historical stats:");
+        for (NetworkStatsHistory stats : mIfaceStats.values()) {
+            stats.dump("  ", pw);
+        }
+    }
+
+    /**
+     * Details for a well-known network interface, including its name, network
+     * type, and billing relationship identity (such as IMSI).
+     */
+    private static class InterfaceInfo {
+        public final String iface;
+        public final int networkType;
+        public final String identity;
+
+        public InterfaceInfo(String iface, int networkType, String identity) {
+            this.iface = iface;
+            this.networkType = networkType;
+            this.identity = identity;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((identity == null) ? 0 : identity.hashCode());
+            result = prime * result + ((iface == null) ? 0 : iface.hashCode());
+            result = prime * result + networkType;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof InterfaceInfo) {
+                final InterfaceInfo info = (InterfaceInfo) obj;
+                return equal(iface, info.iface) && networkType == info.networkType
+                        && equal(identity, info.identity);
+            }
+            return false;
+        }
+
+        public void dump(String prefix, PrintWriter pw) {
+            pw.print(prefix);
+            pw.print("InterfaceInfo: iface="); pw.print(iface);
+            pw.print(" networkType="); pw.print(networkType);
+            pw.print(" identity="); pw.println(identity);
+        }
+    }
+
+    private void reportActiveInterface(InterfaceInfo info) {
+        synchronized (mStatsLock) {
+            // TODO: when interface redefined, port over historical stats
+            mActiveIface.put(info.iface, info);
+        }
+    }
+
+    /**
+     * Return the delta between two {@link NetworkStats} snapshots, where {@code
+     * before} can be {@code null}.
+     */
+    private static NetworkStats computeStatsDelta(NetworkStats before, NetworkStats current) {
+        if (before != null) {
+            return current.subtract(before, false);
+        } else {
+            return current;
+        }
+    }
+
+    private static boolean equal(Object a, Object b) {
+        return a == b || (a != null && a.equals(b));
+    }
+
+    private static <T> T checkNotNull(T value, String message) {
+        if (value == null) {
+            throw new NullPointerException(message);
+        }
+        return value;
+    }
+
+}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index f8d1426..295d569 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -24,7 +24,9 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+    <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+
     <application>
         <uses-library android:name="android.test.runner" />
 
diff --git a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
new file mode 100644
index 0000000..fe88793
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.AbstractFuture;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Future;
+
+/**
+ * {@link ContextWrapper} that can attach listeners for upcoming
+ * {@link Context#sendBroadcast(Intent)}.
+ */
+public class BroadcastInterceptingContext extends ContextWrapper {
+    private static final String TAG = "WatchingContext";
+
+    private final List<BroadcastInterceptor> mInterceptors = Lists.newArrayList();
+
+    public class BroadcastInterceptor extends AbstractFuture<Intent> {
+        private final BroadcastReceiver mReceiver;
+        private final IntentFilter mFilter;
+
+        public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter) {
+            mReceiver = receiver;
+            mFilter = filter;
+        }
+
+        public boolean dispatchBroadcast(Intent intent) {
+            if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
+                if (mReceiver != null) {
+                    final Context context = BroadcastInterceptingContext.this;
+                    mReceiver.onReceive(context, intent);
+                    return false;
+                } else {
+                    set(intent);
+                    return true;
+                }
+            } else {
+                return false;
+            }
+        }
+    }
+
+    public BroadcastInterceptingContext(Context base) {
+        super(base);
+    }
+
+    public Future<Intent> nextBroadcastIntent(String action) {
+        return nextBroadcastIntent(new IntentFilter(action));
+    }
+
+    public Future<Intent> nextBroadcastIntent(IntentFilter filter) {
+        final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter);
+        synchronized (mInterceptors) {
+            mInterceptors.add(interceptor);
+        }
+        return interceptor;
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+        synchronized (mInterceptors) {
+            mInterceptors.add(new BroadcastInterceptor(receiver, filter));
+        }
+        return null;
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+            String broadcastPermission, Handler scheduler) {
+        return registerReceiver(receiver, filter);
+    }
+
+    @Override
+    public void unregisterReceiver(BroadcastReceiver receiver) {
+        synchronized (mInterceptors) {
+            final Iterator<BroadcastInterceptor> i = mInterceptors.iterator();
+            while (i.hasNext()) {
+                final BroadcastInterceptor interceptor = i.next();
+                if (receiver.equals(interceptor.mReceiver)) {
+                    i.remove();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void sendBroadcast(Intent intent) {
+        synchronized (mInterceptors) {
+            final Iterator<BroadcastInterceptor> i = mInterceptors.iterator();
+            while (i.hasNext()) {
+                final BroadcastInterceptor interceptor = i.next();
+                if (interceptor.dispatchBroadcast(intent)) {
+                    i.remove();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void sendStickyBroadcast(Intent intent) {
+        sendBroadcast(intent);
+    }
+
+    @Override
+    public void removeStickyBroadcast(Intent intent) {
+        // ignored
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
new file mode 100644
index 0000000..6552cdf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+
+import android.app.IActivityManager;
+import android.app.IProcessObserver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkStatsService;
+import android.os.Binder;
+import android.os.IPowerManager;
+import android.test.AndroidTestCase;
+import android.test.mock.MockPackageManager;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
+
+import com.android.server.net.NetworkPolicyManagerService;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+
+import java.util.concurrent.Future;
+
+/**
+ * Tests for {@link NetworkPolicyManagerService}.
+ */
+@LargeTest
+public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
+    private static final String TAG = "NetworkPolicyManagerServiceTest";
+
+    private BroadcastInterceptingContext mServiceContext;
+
+    private IActivityManager mActivityManager;
+    private IPowerManager mPowerManager;
+    private INetworkStatsService mStatsService;
+    private INetworkPolicyListener mPolicyListener;
+
+    private NetworkPolicyManagerService mService;
+    private IProcessObserver mProcessObserver;
+
+    private Binder mStubBinder = new Binder();
+
+    private static final int UID_A = 800;
+    private static final int UID_B = 801;
+
+    private static final int PID_1 = 400;
+    private static final int PID_2 = 401;
+    private static final int PID_3 = 402;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // intercept various broadcasts, and pretend that uids have packages
+        mServiceContext = new BroadcastInterceptingContext(getContext()) {
+            @Override
+            public PackageManager getPackageManager() {
+                return new MockPackageManager() {
+                    @Override
+                    public String[] getPackagesForUid(int uid) {
+                        return new String[] { "com.example" };
+                    }
+                };
+            }
+        };
+
+        mActivityManager = createMock(IActivityManager.class);
+        mPowerManager = createMock(IPowerManager.class);
+        mStatsService = createMock(INetworkStatsService.class);
+        mPolicyListener = createMock(INetworkPolicyListener.class);
+
+        mService = new NetworkPolicyManagerService(
+                mServiceContext, mActivityManager, mPowerManager, mStatsService);
+
+        // RemoteCallbackList needs a binder to use as key
+        expect(mPolicyListener.asBinder()).andReturn(mStubBinder).atLeastOnce();
+        replay();
+        mService.registerListener(mPolicyListener);
+        verifyAndReset();
+
+        // catch the registered IProcessObserver during systemReady()
+        final Capture<IProcessObserver> processObserver = new Capture<IProcessObserver>();
+        mActivityManager.registerProcessObserver(capture(processObserver));
+        expectLastCall().atLeastOnce();
+
+        // expect to answer screen status during systemReady()
+        expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
+
+        replay();
+        mService.systemReady();
+        verifyAndReset();
+
+        mProcessObserver = processObserver.getValue();
+
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mServiceContext = null;
+
+        mActivityManager = null;
+        mPowerManager = null;
+        mStatsService = null;
+        mPolicyListener = null;
+
+        mService = null;
+        mProcessObserver = null;
+
+        super.tearDown();
+    }
+
+    @Suppress
+    public void testPolicyChangeTriggersBroadcast() throws Exception {
+        mService.setUidPolicy(UID_A, POLICY_NONE);
+
+        // change background policy and expect broadcast
+        final Future<Intent> backgroundChanged = mServiceContext.nextBroadcastIntent(
+                ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
+
+        mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+
+        backgroundChanged.get();
+    }
+
+    public void testPidForegroundCombined() throws Exception {
+        // push all uid into background
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+        mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, false);
+        assertFalse(mService.isUidForeground(UID_A));
+        assertFalse(mService.isUidForeground(UID_B));
+
+        // push one of the shared pids into foreground
+        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+        assertTrue(mService.isUidForeground(UID_A));
+        assertFalse(mService.isUidForeground(UID_B));
+
+        // and swap another uid into foreground
+        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+        mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, true);
+        assertFalse(mService.isUidForeground(UID_A));
+        assertTrue(mService.isUidForeground(UID_B));
+
+        // push both pid into foreground
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+        assertTrue(mService.isUidForeground(UID_A));
+
+        // pull one out, should still be foreground
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        assertTrue(mService.isUidForeground(UID_A));
+
+        // pull final pid out, should now be background
+        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+        assertFalse(mService.isUidForeground(UID_A));
+    }
+
+    public void testScreenChangesRules() throws Exception {
+        // push strict policy for foreground uid, verify ALLOW rule
+        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+        verifyAndReset();
+
+        // now turn screen off and verify REJECT rule
+        expect(mPowerManager.isScreenOn()).andReturn(false).atLeastOnce();
+        expectRulesChanged(UID_A, RULE_REJECT_PAID);
+        replay();
+        mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
+        verifyAndReset();
+
+        // and turn screen back on, verify ALLOW rule restored
+        expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
+        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
+        verifyAndReset();
+    }
+
+    public void testPolicyNone() throws Exception {
+        // POLICY_NONE should RULE_ALLOW in foreground
+        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mService.setUidPolicy(UID_A, POLICY_NONE);
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        verifyAndReset();
+
+        // POLICY_NONE should RULE_ALLOW in background
+        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        verifyAndReset();
+    }
+
+    public void testPolicyReject() throws Exception {
+        // POLICY_REJECT should RULE_ALLOW in background
+        expectRulesChanged(UID_A, RULE_REJECT_PAID);
+        replay();
+        mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+        verifyAndReset();
+
+        // POLICY_REJECT should RULE_ALLOW in foreground
+        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        verifyAndReset();
+
+        // POLICY_REJECT should RULE_REJECT in background
+        expectRulesChanged(UID_A, RULE_REJECT_PAID);
+        replay();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        verifyAndReset();
+    }
+
+    public void testPolicyRejectAddRemove() throws Exception {
+        // POLICY_NONE should have RULE_ALLOW in background
+        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mService.setUidPolicy(UID_A, POLICY_NONE);
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        verifyAndReset();
+
+        // adding POLICY_REJECT should cause RULE_REJECT
+        expectRulesChanged(UID_A, RULE_REJECT_PAID);
+        replay();
+        mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+        verifyAndReset();
+
+        // removing POLICY_REJECT should return us to RULE_ALLOW
+        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mService.setUidPolicy(UID_A, POLICY_NONE);
+        verifyAndReset();
+    }
+
+    private void expectRulesChanged(int uid, int policy) throws Exception {
+        mPolicyListener.onRulesChanged(eq(uid), eq(policy));
+        expectLastCall().atLeastOnce();
+    }
+
+    private void replay() {
+        EasyMock.replay(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
+    }
+
+    private void verifyAndReset() {
+        EasyMock.verify(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
+        EasyMock.reset(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
index ca33d32..d1ee4f6 100644
--- a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
@@ -25,14 +25,9 @@
 import static org.easymock.EasyMock.reset;
 import static org.easymock.EasyMock.verify;
 
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.AbstractFuture;
-
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.INetworkManagementEventObserver;
 import android.net.NetworkStats;
 import android.net.ThrottleManager;
@@ -48,8 +43,6 @@
 import android.util.Log;
 import android.util.TrustedTime;
 
-import java.util.Iterator;
-import java.util.List;
 import java.util.concurrent.Future;
 
 /**
@@ -66,7 +59,7 @@
 
     private static final String TEST_IFACE = "test0";
 
-    private WatchingContext mWatchingContext;
+    private BroadcastInterceptingContext mWatchingContext;
     private INetworkManagementService mMockNMService;
     private TrustedTime mMockTime;
 
@@ -76,7 +69,7 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        mWatchingContext = new WatchingContext(getContext());
+        mWatchingContext = new BroadcastInterceptingContext(getContext());
 
         mMockNMService = createMock(INetworkManagementService.class);
         mMockTime = createMock(TrustedTime.class);
@@ -354,69 +347,4 @@
 
         pollAction.get();
     }
-
-
-    /**
-     * {@link ContextWrapper} that can attach listeners for upcoming
-     * {@link Context#sendBroadcast(Intent)}.
-     */
-    private static class WatchingContext extends ContextWrapper {
-        private List<LocalBroadcastReceiver> mReceivers = Lists.newArrayList();
-
-        public class LocalBroadcastReceiver extends AbstractFuture<Intent> {
-            private IntentFilter mFilter;
-
-            public LocalBroadcastReceiver(IntentFilter filter) {
-                mFilter = filter;
-            }
-
-            public boolean dispatchBroadcast(Intent intent) {
-                if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
-                    set(intent);
-                    return true;
-                } else {
-                    return false;
-                }
-            }
-        }
-
-        public WatchingContext(Context base) {
-            super(base);
-        }
-
-        public Future<Intent> nextBroadcastIntent(String action) {
-            return nextBroadcastIntent(new IntentFilter(action));
-        }
-
-        public Future<Intent> nextBroadcastIntent(IntentFilter filter) {
-            final LocalBroadcastReceiver receiver = new LocalBroadcastReceiver(filter);
-            synchronized (mReceivers) {
-                mReceivers.add(receiver);
-            }
-            return receiver;
-        }
-
-        @Override
-        public void sendBroadcast(Intent intent) {
-            synchronized (mReceivers) {
-                final Iterator<LocalBroadcastReceiver> i = mReceivers.iterator();
-                while (i.hasNext()) {
-                    final LocalBroadcastReceiver receiver = i.next();
-                    if (receiver.dispatchBroadcast(intent)) {
-                        i.remove();
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void sendStickyBroadcast(Intent intent) {
-            sendBroadcast(intent);
-        }
-
-        @Override
-        public void removeStickyBroadcast(Intent intent) {
-            // ignored
-        }
-    }
 }
diff --git a/tests/BiDiTests/AndroidManifest.xml b/tests/BiDiTests/AndroidManifest.xml
index e54194c..b3d8893 100644
--- a/tests/BiDiTests/AndroidManifest.xml
+++ b/tests/BiDiTests/AndroidManifest.xml
@@ -57,6 +57,20 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".BiDiTestFrameLayoutLtrActivity"
+                  android:windowSoftInputMode="stateAlwaysHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".BiDiTestFrameLayoutRtlActivity"
+                  android:windowSoftInputMode="stateAlwaysHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/BiDiTests/res/layout/frame_layout_ltr.xml b/tests/BiDiTests/res/layout/frame_layout_ltr.xml
new file mode 100644
index 0000000..c27b260
--- /dev/null
+++ b/tests/BiDiTests/res/layout/frame_layout_ltr.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/frame_layout_ltr"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:horizontalDirection="ltr"
+    android:background="#FF000000">
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="right|center_vertical"
+            android:background="#FFFF0000">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="left|center_vertical"
+            android:background="#FF00FF00">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="top|center_horizontal"
+            android:background="#FF0000FF">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="bottom|center_horizontal"
+            android:background="#FF00FFFF">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="top|before"
+            android:background="#FFFFFFFF">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="top|after"
+            android:background="#FFFFFF00">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="bottom|before"
+            android:background="#FFFFFFFF">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="bottom|after"
+            android:background="#FFFFFF00">
+        </RelativeLayout>
+
+</FrameLayout>
+
diff --git a/tests/BiDiTests/res/layout/frame_layout_rtl.xml b/tests/BiDiTests/res/layout/frame_layout_rtl.xml
new file mode 100644
index 0000000..33ca40a
--- /dev/null
+++ b/tests/BiDiTests/res/layout/frame_layout_rtl.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/frame_layout_rtl"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:horizontalDirection="rtl"
+    android:background="#FF000000">
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="right|center_vertical"
+            android:background="#FFFF0000">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="left|center_vertical"
+            android:background="#FF00FF00">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="top|center_horizontal"
+            android:background="#FF0000FF">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="bottom|center_horizontal"
+            android:background="#FF00FFFF">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="top|before"
+            android:background="#FFFFFFFF">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="top|after"
+            android:background="#FFFFFF00">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="bottom|before"
+            android:background="#FFFFFFFF">
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_gravity="bottom|after"
+            android:background="#FFFFFF00">
+        </RelativeLayout>
+
+</FrameLayout>
+
diff --git a/tests/BiDiTests/res/layout/linear_layout_ltr.xml b/tests/BiDiTests/res/layout/linear_layout_ltr.xml
index a95fb0e..1370ae1c 100644
--- a/tests/BiDiTests/res/layout/linear_layout_ltr.xml
+++ b/tests/BiDiTests/res/layout/linear_layout_ltr.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/layouttest"
+    android:id="@+id/linear_layout_ltr"
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
diff --git a/tests/BiDiTests/res/layout/linear_layout_rtl.xml b/tests/BiDiTests/res/layout/linear_layout_rtl.xml
index 0d60950..6044f16 100644
--- a/tests/BiDiTests/res/layout/linear_layout_rtl.xml
+++ b/tests/BiDiTests/res/layout/linear_layout_rtl.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/layouttest"
+    android:id="@+id/linear_layout_rtl"
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
index 0d9b4f7..d89b8fd 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
@@ -37,20 +37,34 @@
         intent = new Intent().setClass(this, BiDiTestBasicActivity.class);
 
         // Initialize a TabSpec for each tab and add it to the TabHost
-        spec = tabHost.newTabSpec("basic").setIndicator("Basic").setContent(intent);
+        spec = tabHost.newTabSpec("basic").setIndicator("Basic").
+            setContent(intent);
         tabHost.addTab(spec);
 
         // Do the same for the other tabs
         intent = new Intent().setClass(this, BiDiTestCanvasActivity.class);
-        spec = tabHost.newTabSpec("canvas").setIndicator("Canvas").setContent(intent);
+        spec = tabHost.newTabSpec("canvas").setIndicator("Canvas").
+            setContent(intent);
         tabHost.addTab(spec);
 
         intent = new Intent().setClass(this, BiDiTestLinearLayoutLtrActivity.class);
-        spec = tabHost.newTabSpec("layout-ltr").setIndicator("LinearLayout LTR").setContent(intent);
+        spec = tabHost.newTabSpec("linear-layout-ltr").setIndicator("LinearLayout LTR").
+            setContent(intent);
         tabHost.addTab(spec);
 
         intent = new Intent().setClass(this, BiDiTestLinearLayoutRtlActivity.class);
-        spec = tabHost.newTabSpec("layout-rtl").setIndicator("LinearLayout RTL").setContent(intent);
+        spec = tabHost.newTabSpec("linear-layout-rtl").setIndicator("LinearLayout RTL").
+            setContent(intent);
+        tabHost.addTab(spec);
+
+        intent = new Intent().setClass(this, BiDiTestFrameLayoutLtrActivity.class);
+        spec = tabHost.newTabSpec("frame-layout-ltr").setIndicator("FrameLayout LTR").
+            setContent(intent);
+        tabHost.addTab(spec);
+
+        intent = new Intent().setClass(this, BiDiTestFrameLayoutRtlActivity.class);
+        spec = tabHost.newTabSpec("frame-layout-rtl").setIndicator("FrameLayout RTL").
+            setContent(intent);
         tabHost.addTab(spec);
 
         tabHost.setCurrentTab(0);
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutLtrActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutLtrActivity.java
new file mode 100644
index 0000000..6ce4fe4
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutLtrActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bidi;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class BiDiTestFrameLayoutLtrActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.frame_layout_ltr);
+    }
+}
+
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutRtlActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutRtlActivity.java
new file mode 100644
index 0000000..6012a5c
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutRtlActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bidi;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class BiDiTestFrameLayoutRtlActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.frame_layout_rtl);
+    }
+}
+
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutLtrActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutLtrActivity.java
index 6d8f11d..280af6a 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutLtrActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutLtrActivity.java
@@ -18,18 +18,13 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.widget.LinearLayout;
 
 public class BiDiTestLinearLayoutLtrActivity extends Activity {
 
-    private LinearLayout layout;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         setContentView(R.layout.linear_layout_ltr);
-
-        layout = (LinearLayout) findViewById(R.id.layouttest);
     }
 }
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutRtlActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutRtlActivity.java
index 0130793..7121a62 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutRtlActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutRtlActivity.java
@@ -18,18 +18,13 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.widget.LinearLayout;
 
 public class BiDiTestLinearLayoutRtlActivity extends Activity {
 
-    private LinearLayout layout;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         setContentView(R.layout.linear_layout_rtl);
-
-        layout = (LinearLayout) findViewById(R.id.layouttest);
     }
 }