Merge "Add map center resources for new locales."
diff --git a/api/current.txt b/api/current.txt
index 51c9348..d490e29 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -696,8 +696,10 @@
     field public static final int packageNames = 16843651; // 0x1010383
     field public static final int padding = 16842965; // 0x10100d5
     field public static final int paddingBottom = 16842969; // 0x10100d9
+    field public static final int paddingEnd = 16843673; // 0x1010399
     field public static final int paddingLeft = 16842966; // 0x10100d6
     field public static final int paddingRight = 16842968; // 0x10100d8
+    field public static final int paddingStart = 16843672; // 0x1010398
     field public static final int paddingTop = 16842967; // 0x10100d7
     field public static final int panelBackground = 16842846; // 0x101005e
     field public static final int panelColorBackground = 16842849; // 0x1010061
@@ -16663,10 +16665,25 @@
     method public static android.renderscript.Element F32_3(android.renderscript.RenderScript);
     method public static android.renderscript.Element F32_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element F64(android.renderscript.RenderScript);
+    method public static android.renderscript.Element F64_2(android.renderscript.RenderScript);
+    method public static android.renderscript.Element F64_3(android.renderscript.RenderScript);
+    method public static android.renderscript.Element F64_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element I16(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I16_2(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I16_3(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I16_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element I32(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I32_2(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I32_3(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I32_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element I64(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I64_2(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I64_3(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I64_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element I8(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I8_2(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I8_3(android.renderscript.RenderScript);
+    method public static android.renderscript.Element I8_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element MATRIX4X4(android.renderscript.RenderScript);
     method public static android.renderscript.Element MATRIX_2X2(android.renderscript.RenderScript);
     method public static android.renderscript.Element MATRIX_3X3(android.renderscript.RenderScript);
@@ -16685,9 +16702,20 @@
     method public static android.renderscript.Element SCRIPT(android.renderscript.RenderScript);
     method public static android.renderscript.Element TYPE(android.renderscript.RenderScript);
     method public static android.renderscript.Element U16(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U16_2(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U16_3(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U16_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element U32(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U32_2(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U32_3(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U32_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element U64(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U64_2(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U64_3(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U64_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element U8(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U8_2(android.renderscript.RenderScript);
+    method public static android.renderscript.Element U8_3(android.renderscript.RenderScript);
     method public static android.renderscript.Element U8_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element createPixel(android.renderscript.RenderScript, android.renderscript.Element.DataType, android.renderscript.Element.DataKind);
     method public static android.renderscript.Element createVector(android.renderscript.RenderScript, android.renderscript.Element.DataType, int);
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 28559cc..ad8d41f 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -226,6 +226,14 @@
     public final static int ERROR_FILE_ALREADY_EXISTS = 1009;
 
     /**
+     * Value of {@link #COLUMN_REASON} when the download has failed because of
+     * {@link NetworkPolicyManager} controls on the requesting application.
+     *
+     * @hide
+     */
+    public final static int ERROR_BLOCKED = 1010;
+
+    /**
      * Value of {@link #COLUMN_REASON} when the download is paused because some network error
      * occurred and the download manager is waiting before retrying the request.
      */
@@ -249,14 +257,6 @@
     public final static int PAUSED_UNKNOWN = 4;
 
     /**
-     * Value of {@link #COLUMN_REASON} when the download has been paused because
-     * of {@link NetworkPolicyManager} controls on the requesting application.
-     *
-     * @hide
-     */
-    public final static int PAUSED_BY_POLICY = 5;
-
-    /**
      * Broadcast intent action sent by the download manager when a download completes.
      */
     public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
@@ -804,7 +804,6 @@
                     parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY));
                     parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
                     parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
-                    parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_POLICY));
                 }
                 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
                     parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS));
@@ -1275,9 +1274,6 @@
                 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
                     return PAUSED_QUEUED_FOR_WIFI;
 
-                case Downloads.Impl.STATUS_PAUSED_BY_POLICY:
-                    return PAUSED_BY_POLICY;
-
                 default:
                     return PAUSED_UNKNOWN;
             }
@@ -1316,6 +1312,9 @@
                 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
                     return ERROR_FILE_ALREADY_EXISTS;
 
+                case Downloads.Impl.STATUS_BLOCKED:
+                    return ERROR_BLOCKED;
+
                 default:
                     return ERROR_UNKNOWN;
             }
@@ -1333,7 +1332,6 @@
                 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
                 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
                 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
-                case Downloads.Impl.STATUS_PAUSED_BY_POLICY:
                     return STATUS_PAUSED;
 
                 case Downloads.Impl.STATUS_SUCCESS:
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 5df2343..2b9c082 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -81,4 +81,13 @@
 
     /* Clears default preferences and permissions for the package */
     void clearDefaults(String packageName);
+
+    /* Sets the current primary USB function. */
+    void setPrimaryFunction(String functions);
+
+    /* Sets the default primary USB function. */
+    void setDefaultFunction(String functions);
+
+    /* Sets the file path for USB mass storage backing file. */
+    void setMassStorageBackingFile(String path);
 }
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 5994c98..a828a23 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -22,12 +22,9 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.Log;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.util.HashMap;
 
 /**
@@ -50,7 +47,7 @@
      * This is a sticky broadcast for clients that includes USB connected/disconnected state,
      * <ul>
      * <li> {@link #USB_CONNECTED} boolean indicating whether USB is connected or disconnected.
-     * <li> {@link #USB_CONFIGURATION} integer containing current USB configuration
+     * <li> {@link #USB_CONFIGURED} boolean indicating whether USB is configured.
      * currently zero if not configured, one for configured.
      * <li> {@link #USB_FUNCTION_MASS_STORAGE} boolean extra indicating whether the
      * mass storage function is enabled
@@ -128,12 +125,12 @@
     public static final String USB_CONNECTED = "connected";
 
     /**
-     * Integer extra containing currently set USB configuration.
+     * Boolean extra indicating whether USB is configured.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
      * {@hide}
      */
-    public static final String USB_CONFIGURATION = "configuration";
+    public static final String USB_CONFIGURED = "configured";
 
     /**
      * Name of the USB mass storage USB function.
@@ -388,21 +385,14 @@
         }
     }
 
-    private static File getFunctionEnableFile(String function) {
-        return new File("/sys/class/usb_composite/" + function + "/enable");
-    }
-
-    /**
-     * Returns true if the specified USB function is supported by the kernel.
-     * Note that a USB function maybe supported but disabled.
-     *
-     * @param function name of the USB function
-     * @return true if the USB function is supported.
-     *
-     * {@hide}
-     */
-    public static boolean isFunctionSupported(String function) {
-        return getFunctionEnableFile(function).exists();
+    private static boolean propertyContainsFunction(String property, String function) {
+        String functions = SystemProperties.get(property, "");
+        int index = functions.indexOf(function);
+        if (index < 0) return false;
+        if (index > 0 && functions.charAt(index - 1) != ',') return false;
+        int charAfter = index + function.length();
+        if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
+        return true;
     }
 
     /**
@@ -413,30 +403,52 @@
      *
      * {@hide}
      */
-    public static boolean isFunctionEnabled(String function) {
+    public boolean isFunctionEnabled(String function) {
+        return propertyContainsFunction("sys.usb.config", function);
+    }
+
+    /**
+     * Sets the primary USB function.
+     *
+     * @param function name of the USB function
+     *
+     * {@hide}
+     */
+    public void setPrimaryFunction(String function) {
         try {
-            FileInputStream stream = new FileInputStream(getFunctionEnableFile(function));
-            boolean enabled = (stream.read() == '1');
-            stream.close();
-            return enabled;
-        } catch (IOException e) {
-            return false;
+            mService.setPrimaryFunction(function);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in setPrimaryFunction", e);
         }
     }
 
     /**
-     * Enables or disables a USB function.
+     * Sets the default primary USB function.
+     *
+     * @param function name of the USB function
      *
      * {@hide}
      */
-    public static boolean setFunctionEnabled(String function, boolean enable) {
+    public void setDefaultFunction(String function) {
         try {
-            FileOutputStream stream = new FileOutputStream(getFunctionEnableFile(function));
-            stream.write(enable ? '1' : '0');
-            stream.close();
-            return true;
-        } catch (IOException e) {
-            return false;
+            mService.setDefaultFunction(function);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in setDefaultFunction", e);
+        }
+    }
+
+    /**
+     * Sets the file path for USB mass storage backing file.
+     *
+     * @param path backing file path
+     *
+     * {@hide}
+     */
+    public void setMassStorageBackingFile(String path) {
+        try {
+            mService.setMassStorageBackingFile(path);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in setDefaultFunction", e);
         }
     }
 }
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
index 9230151..a45ec54 100644
--- a/core/java/android/net/INetworkPolicyListener.aidl
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -19,6 +19,7 @@
 /** {@hide} */
 oneway interface INetworkPolicyListener {
 
-    void onRulesChanged(int uid, int uidRules);
+    void onUidRulesChanged(int uid, int uidRules);
+    void onMeteredIfacesChanged(in String[] meteredIfaces);
 
 }
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 288112a..ae9aa05 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -18,18 +18,19 @@
 
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
 
 /** {@hide} */
 interface INetworkStatsService {
 
     /** Return historical stats for traffic that matches template. */
-    NetworkStatsHistory getHistoryForNetwork(int networkTemplate);
+    NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template);
     /** Return historical stats for specific UID traffic that matches template. */
-    NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate);
+    NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int tag);
 
     /** Return usage summary for traffic that matches template. */
-    NetworkStats getSummaryForNetwork(long start, long end, int networkTemplate, String subscriberId);
+    NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end);
     /** Return usage summary per UID for traffic that matches template. */
-    NetworkStats getSummaryForAllUid(long start, long end, int networkTemplate);
+    NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags);
 
 }
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
new file mode 100644
index 0000000..ccef122
--- /dev/null
+++ b/core/java/android/net/NetworkIdentity.java
@@ -0,0 +1,122 @@
+/*
+ * 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.isNetworkTypeMobile;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.util.Objects;
+
+/**
+ * Network definition that includes strong identity. Analogous to combining
+ * {@link NetworkInfo} and an IMSI.
+ *
+ * @hide
+ */
+public class NetworkIdentity {
+    final int mType;
+    final int mSubType;
+    final String mSubscriberId;
+    final boolean mRoaming;
+
+    public NetworkIdentity(int type, int subType, String subscriberId, boolean roaming) {
+        this.mType = type;
+        this.mSubType = subType;
+        this.mSubscriberId = subscriberId;
+        this.mRoaming = roaming;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mType, mSubType, mSubscriberId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof NetworkIdentity) {
+            final NetworkIdentity ident = (NetworkIdentity) obj;
+            return mType == ident.mType && mSubType == ident.mSubType
+                    && Objects.equal(mSubscriberId, ident.mSubscriberId)
+                    && mRoaming == ident.mRoaming;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        final String typeName = ConnectivityManager.getNetworkTypeName(mType);
+        final String subTypeName;
+        if (ConnectivityManager.isNetworkTypeMobile(mType)) {
+            subTypeName = TelephonyManager.getNetworkTypeName(mSubType);
+        } else {
+            subTypeName = Integer.toString(mSubType);
+        }
+
+        final String scrubSubscriberId = mSubscriberId != null ? "valid" : "null";
+        final String roaming = mRoaming ? ", ROAMING" : "";
+        return "[type=" + typeName + ", subType=" + subTypeName + ", subscriberId="
+                + scrubSubscriberId + roaming + "]";
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    public int getSubType() {
+        return mSubType;
+    }
+
+    public String getSubscriberId() {
+        return mSubscriberId;
+    }
+
+    public boolean getRoaming() {
+        return mRoaming;
+    }
+
+    /**
+     * Build a {@link NetworkIdentity} from the given {@link NetworkState},
+     * assuming that any mobile networks are using the current IMSI.
+     */
+    public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state) {
+        final int type = state.networkInfo.getType();
+        final int subType = state.networkInfo.getSubtype();
+
+        // TODO: consider moving subscriberId over to LinkCapabilities, so it
+        // comes from an authoritative source.
+
+        final String subscriberId;
+        final boolean roaming;
+        if (isNetworkTypeMobile(type)) {
+            final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+            roaming = telephony.isNetworkRoaming();
+            if (state.subscriberId != null) {
+                subscriberId = state.subscriberId;
+            } else {
+                subscriberId = telephony.getSubscriberId();
+            }
+        } else {
+            subscriberId = null;
+            roaming = false;
+        }
+        return new NetworkIdentity(type, subType, subscriberId, roaming);
+    }
+
+}
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index 1899281..52cab30 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -16,37 +16,38 @@
 
 package android.net;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.os.Parcel;
 import android.os.Parcelable;
 
 /**
- * Policy for a specific network, including usage cycle and limits to be
- * enforced.
+ * Policy for networks matching a {@link NetworkTemplate}, including usage cycle
+ * and limits to be enforced.
  *
  * @hide
  */
 public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
-    public final int networkTemplate;
-    public final String subscriberId;
+    public static final long WARNING_DISABLED = -1;
+    public static final long LIMIT_DISABLED = -1;
+
+    public final NetworkTemplate template;
     public int cycleDay;
     public long warningBytes;
     public long limitBytes;
 
-    public static final long WARNING_DISABLED = -1;
-    public static final long LIMIT_DISABLED = -1;
+    // TODO: teach how to snooze limit for current cycle
 
-    public NetworkPolicy(int networkTemplate, String subscriberId, int cycleDay, long warningBytes,
-            long limitBytes) {
-        this.networkTemplate = networkTemplate;
-        this.subscriberId = subscriberId;
+    public NetworkPolicy(
+            NetworkTemplate template, int cycleDay, long warningBytes, long limitBytes) {
+        this.template = checkNotNull(template, "missing NetworkTemplate");
         this.cycleDay = cycleDay;
         this.warningBytes = warningBytes;
         this.limitBytes = limitBytes;
     }
 
     public NetworkPolicy(Parcel in) {
-        networkTemplate = in.readInt();
-        subscriberId = in.readString();
+        template = in.readParcelable(null);
         cycleDay = in.readInt();
         warningBytes = in.readLong();
         limitBytes = in.readLong();
@@ -54,8 +55,7 @@
 
     /** {@inheritDoc} */
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(networkTemplate);
-        dest.writeString(subscriberId);
+        dest.writeParcelable(template, flags);
         dest.writeInt(cycleDay);
         dest.writeLong(warningBytes);
         dest.writeLong(limitBytes);
@@ -81,8 +81,8 @@
 
     @Override
     public String toString() {
-        return "NetworkPolicy: networkTemplate=" + networkTemplate + ", cycleDay=" + cycleDay
-                + ", warningBytes=" + warningBytes + ", limitBytes=" + limitBytes;
+        return "NetworkPolicy[" + template + "]: cycleDay=" + cycleDay + ", warningBytes="
+                + warningBytes + ", limitBytes=" + limitBytes;
     }
 
     public static final Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() {
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 0d4d9a9..91af16d 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -34,13 +34,13 @@
 
     /** No specific network policy, use system default. */
     public static final int POLICY_NONE = 0x0;
-    /** Reject network usage on paid networks when application in background. */
-    public static final int POLICY_REJECT_PAID_BACKGROUND = 0x1;
+    /** Reject network usage on metered networks when application in background. */
+    public static final int POLICY_REJECT_METERED_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;
+    /** Reject traffic on metered networks. */
+    public static final int RULE_REJECT_METERED = 0x1;
 
     /**
      * {@link Intent} action launched when user selects {@link NetworkPolicy}
@@ -59,7 +59,7 @@
     /**
      * {@link Intent} extra included in {@link #ACTION_DATA_USAGE_WARNING} and
      * {@link #ACTION_DATA_USAGE_LIMIT} to indicate which
-     * {@link NetworkPolicy#networkTemplate} it applies to.
+     * {@link NetworkTemplate} rule it applies to.
      */
     public static final String EXTRA_NETWORK_TEMPLATE =
             "android.intent.extra.NETWORK_TEMPLATE";
@@ -98,7 +98,7 @@
      * Set policy flags for specific UID.
      *
      * @param policy {@link #POLICY_NONE} or combination of flags like
-     *            {@link #POLICY_REJECT_PAID_BACKGROUND}.
+     *            {@link #POLICY_REJECT_METERED_BACKGROUND}.
      */
     public void setUidPolicy(int uid, int policy) {
         try {
@@ -217,8 +217,8 @@
     /** {@hide} */
     public static void dumpPolicy(PrintWriter fout, int policy) {
         fout.write("[");
-        if ((policy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
-            fout.write("REJECT_PAID_BACKGROUND");
+        if ((policy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
+            fout.write("REJECT_METERED_BACKGROUND");
         }
         fout.write("]");
     }
@@ -226,8 +226,8 @@
     /** {@hide} */
     public static void dumpRules(PrintWriter fout, int rules) {
         fout.write("[");
-        if ((rules & RULE_REJECT_PAID) != 0) {
-            fout.write("REJECT_PAID");
+        if ((rules & RULE_REJECT_METERED) != 0) {
+            fout.write("REJECT_METERED");
         }
         fout.write("]");
     }
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index 749039a..704111b 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -29,18 +29,27 @@
     public final NetworkInfo networkInfo;
     public final LinkProperties linkProperties;
     public final LinkCapabilities linkCapabilities;
+    /** Currently only used by testing. */
+    public final String subscriberId;
 
     public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
             LinkCapabilities linkCapabilities) {
+        this(networkInfo, linkProperties, linkCapabilities, null);
+    }
+
+    public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
+            LinkCapabilities linkCapabilities, String subscriberId) {
         this.networkInfo = networkInfo;
         this.linkProperties = linkProperties;
         this.linkCapabilities = linkCapabilities;
+        this.subscriberId = subscriberId;
     }
 
     public NetworkState(Parcel in) {
         networkInfo = in.readParcelable(null);
         linkProperties = in.readParcelable(null);
         linkCapabilities = in.readParcelable(null);
+        subscriberId = in.readString();
     }
 
     /** {@inheritDoc} */
@@ -53,6 +62,7 @@
         out.writeParcelable(networkInfo, flags);
         out.writeParcelable(linkProperties, flags);
         out.writeParcelable(linkCapabilities, flags);
+        out.writeString(subscriberId);
     }
 
     public static final Creator<NetworkState> CREATOR = new Creator<NetworkState>() {
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 60f740e..9d40c42 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -40,9 +40,8 @@
     public static final String IFACE_ALL = null;
     /** {@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.
+    /** {@link #tag} value for without tag. */
+    public static final int TAG_NONE = 0;
 
     /**
      * {@link SystemClock#elapsedRealtime()} timestamp when this data was
@@ -52,16 +51,16 @@
     public int size;
     public String[] iface;
     public int[] uid;
+    public int[] tag;
     public long[] rx;
     public long[] tx;
 
-    // TODO: add fg/bg stats once reported by kernel
-
     public NetworkStats(long elapsedRealtime, int initialSize) {
         this.elapsedRealtime = elapsedRealtime;
         this.size = 0;
         this.iface = new String[initialSize];
         this.uid = new int[initialSize];
+        this.tag = new int[initialSize];
         this.rx = new long[initialSize];
         this.tx = new long[initialSize];
     }
@@ -71,21 +70,27 @@
         size = parcel.readInt();
         iface = parcel.createStringArray();
         uid = parcel.createIntArray();
+        tag = parcel.createIntArray();
         rx = parcel.createLongArray();
         tx = parcel.createLongArray();
     }
 
-    public NetworkStats addEntry(String iface, int uid, long rx, long tx) {
+    /**
+     * Add new stats entry with given values.
+     */
+    public NetworkStats addEntry(String iface, int uid, int tag, long rx, long tx) {
         if (size >= this.iface.length) {
             final int newLength = Math.max(this.iface.length, 10) * 3 / 2;
             this.iface = Arrays.copyOf(this.iface, newLength);
             this.uid = Arrays.copyOf(this.uid, newLength);
+            this.tag = Arrays.copyOf(this.tag, newLength);
             this.rx = Arrays.copyOf(this.rx, newLength);
             this.tx = Arrays.copyOf(this.tx, newLength);
         }
 
         this.iface[size] = iface;
         this.uid[size] = uid;
+        this.tag[size] = tag;
         this.rx[size] = rx;
         this.tx[size] = tx;
         size++;
@@ -93,17 +98,29 @@
         return this;
     }
 
-    @Deprecated
-    public int length() {
-        return size;
+    /**
+     * Combine given values with an existing row, or create a new row if
+     * {@link #findIndex(String, int, int)} is unable to find match. Can also be
+     * used to subtract values from existing rows.
+     */
+    public NetworkStats combineEntry(String iface, int uid, int tag, long rx, long tx) {
+        final int i = findIndex(iface, uid, tag);
+        if (i == -1) {
+            // only create new entry when positive contribution
+            addEntry(iface, uid, tag, rx, tx);
+        } else {
+            this.rx[i] += rx;
+            this.tx[i] += tx;
+        }
+        return this;
     }
 
     /**
      * Find first stats index that matches the requested parameters.
      */
-    public int findIndex(String iface, int uid) {
+    public int findIndex(String iface, int uid, int tag) {
         for (int i = 0; i < size; i++) {
-            if (equal(iface, this.iface[i]) && uid == this.uid[i]) {
+            if (equal(iface, this.iface[i]) && uid == this.uid[i] && tag == this.tag[i]) {
                 return i;
             }
         }
@@ -186,12 +203,13 @@
         for (int i = 0; i < size; i++) {
             final String iface = this.iface[i];
             final int uid = this.uid[i];
+            final int tag = this.tag[i];
 
             // find remote row that matches, and subtract
-            final int j = value.findIndex(iface, uid);
+            final int j = value.findIndex(iface, uid, tag);
             if (j == -1) {
                 // newly appearing row, return entire value
-                result.addEntry(iface, uid, this.rx[i], this.tx[i]);
+                result.addEntry(iface, uid, tag, this.rx[i], this.tx[i]);
             } else {
                 // existing row, subtract remote value
                 long rx = this.rx[i] - value.rx[j];
@@ -203,7 +221,7 @@
                     rx = Math.max(0, rx);
                     tx = Math.max(0, tx);
                 }
-                result.addEntry(iface, uid, rx, tx);
+                result.addEntry(iface, uid, tag, rx, tx);
             }
         }
 
@@ -221,6 +239,7 @@
             pw.print(prefix);
             pw.print("  iface="); pw.print(iface[i]);
             pw.print(" uid="); pw.print(uid[i]);
+            pw.print(" tag="); pw.print(tag[i]);
             pw.print(" rx="); pw.print(rx[i]);
             pw.print(" tx="); pw.println(tx[i]);
         }
@@ -244,6 +263,7 @@
         dest.writeInt(size);
         dest.writeStringArray(iface);
         dest.writeIntArray(uid);
+        dest.writeIntArray(tag);
         dest.writeLongArray(rx);
         dest.writeLongArray(tx);
     }
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 5fa8e21..ff6e220 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -40,10 +40,9 @@
  * @hide
  */
 public class NetworkStatsHistory implements Parcelable {
-    private static final int VERSION_CURRENT = 1;
+    private static final int VERSION_INIT = 1;
 
-    // TODO: teach about zigzag encoding to use less disk space
-    // TODO: teach how to convert between bucket sizes
+    // TODO: teach about varint encoding to use less disk space
 
     public final long bucketDuration;
 
@@ -83,7 +82,7 @@
     public NetworkStatsHistory(DataInputStream in) throws IOException {
         final int version = in.readInt();
         switch (version) {
-            case VERSION_CURRENT: {
+            case VERSION_INIT: {
                 bucketDuration = in.readLong();
                 bucketStart = readLongArray(in);
                 rx = readLongArray(in);
@@ -98,7 +97,7 @@
     }
 
     public void writeToStream(DataOutputStream out) throws IOException {
-        out.writeInt(VERSION_CURRENT);
+        out.writeInt(VERSION_INIT);
         out.writeLong(bucketDuration);
         writeLongArray(out, bucketStart, bucketCount);
         writeLongArray(out, rx, bucketCount);
@@ -115,6 +114,11 @@
      * distribute across internal buckets, creating new buckets as needed.
      */
     public void recordData(long start, long end, long rx, long tx) {
+        if (rx < 0 || tx < 0) {
+            throw new IllegalArgumentException(
+                    "tried recording negative data: rx=" + rx + ", tx=" + tx);
+        }
+
         // create any buckets needed by this range
         ensureBuckets(start, end);
 
diff --git a/core/java/android/net/NetworkTemplate.aidl b/core/java/android/net/NetworkTemplate.aidl
new file mode 100644
index 0000000..3d37488
--- /dev/null
+++ b/core/java/android/net/NetworkTemplate.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 NetworkTemplate;
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
new file mode 100644
index 0000000..9381f1d
--- /dev/null
+++ b/core/java/android/net/NetworkTemplate.java
@@ -0,0 +1,217 @@
+/*
+ * 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_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.ConnectivityManager.isNetworkTypeMobile;
+import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
+import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
+import static android.telephony.TelephonyManager.NETWORK_CLASS_4_G;
+import static android.telephony.TelephonyManager.NETWORK_CLASS_UNKNOWN;
+import static android.telephony.TelephonyManager.getNetworkClass;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Objects;
+
+/**
+ * Template definition used to generically match {@link NetworkIdentity},
+ * usually when collecting statistics.
+ *
+ * @hide
+ */
+public class NetworkTemplate implements Parcelable {
+
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
+     * networks together. Only uses statistics for requested IMSI.
+     */
+    public static final int MATCH_MOBILE_ALL = 1;
+
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
+     * networks together that roughly meet a "3G" definition, or lower. Only
+     * uses statistics for requested IMSI.
+     */
+    public static final int MATCH_MOBILE_3G_LOWER = 2;
+
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
+     * networks together that meet a "4G" definition. Only uses statistics for
+     * requested IMSI.
+     */
+    public static final int MATCH_MOBILE_4G = 3;
+
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_WIFI} style
+     * networks together.
+     */
+    public static final int MATCH_WIFI = 4;
+
+    final int mMatchRule;
+    final String mSubscriberId;
+
+    public NetworkTemplate(int matchRule, String subscriberId) {
+        this.mMatchRule = matchRule;
+        this.mSubscriberId = subscriberId;
+    }
+
+    public NetworkTemplate(Parcel in) {
+        mMatchRule = in.readInt();
+        mSubscriberId = in.readString();
+    }
+
+    /** {@inheritDoc} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMatchRule);
+        dest.writeString(mSubscriberId);
+    }
+
+    /** {@inheritDoc} */
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        final String scrubSubscriberId = mSubscriberId != null ? "valid" : "null";
+        return "NetworkTemplate: matchRule=" + getMatchRuleName(mMatchRule) + ", subscriberId="
+                + scrubSubscriberId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mMatchRule, mSubscriberId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof NetworkTemplate) {
+            final NetworkTemplate other = (NetworkTemplate) obj;
+            return mMatchRule == other.mMatchRule
+                    && Objects.equal(mSubscriberId, other.mSubscriberId);
+        }
+        return false;
+    }
+
+    public int getMatchRule() {
+        return mMatchRule;
+    }
+
+    public String getSubscriberId() {
+        return mSubscriberId;
+    }
+
+    /**
+     * Test if this network matches the given template and IMEI.
+     */
+    public boolean matches(NetworkIdentity ident) {
+        switch (mMatchRule) {
+            case MATCH_MOBILE_ALL:
+                return matchesMobile(ident);
+            case MATCH_MOBILE_3G_LOWER:
+                return matchesMobile3gLower(ident);
+            case MATCH_MOBILE_4G:
+                return matchesMobile4g(ident);
+            case MATCH_WIFI:
+                return matchesWifi(ident);
+            default:
+                throw new IllegalArgumentException("unknown network template");
+        }
+    }
+
+    /**
+     * Check if mobile network with matching IMEI. Also matches
+     * {@link #TYPE_WIMAX}.
+     */
+    private boolean matchesMobile(NetworkIdentity ident) {
+        if (isNetworkTypeMobile(ident.mType) && Objects.equal(mSubscriberId, ident.mSubscriberId)) {
+            return true;
+        } else if (ident.mType == TYPE_WIMAX) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if mobile network classified 3G or lower with matching IMEI.
+     */
+    private boolean matchesMobile3gLower(NetworkIdentity ident) {
+        if (isNetworkTypeMobile(ident.mType) && Objects.equal(mSubscriberId, ident.mSubscriberId)) {
+            switch (getNetworkClass(ident.mSubType)) {
+                case NETWORK_CLASS_UNKNOWN:
+                case NETWORK_CLASS_2_G:
+                case NETWORK_CLASS_3_G:
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check if mobile network classified 4G with matching IMEI. Also matches
+     * {@link #TYPE_WIMAX}.
+     */
+    private boolean matchesMobile4g(NetworkIdentity ident) {
+        if (isNetworkTypeMobile(ident.mType) && Objects.equal(mSubscriberId, ident.mSubscriberId)) {
+            switch (getNetworkClass(ident.mSubType)) {
+                case NETWORK_CLASS_4_G:
+                    return true;
+            }
+        } else if (ident.mType == TYPE_WIMAX) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if matches Wi-Fi network template.
+     */
+    private boolean matchesWifi(NetworkIdentity ident) {
+        if (ident.mType == TYPE_WIFI) {
+            return true;
+        }
+        return false;
+    }
+
+    public static String getMatchRuleName(int matchRule) {
+        switch (matchRule) {
+            case MATCH_MOBILE_3G_LOWER:
+                return "MOBILE_3G_LOWER";
+            case MATCH_MOBILE_4G:
+                return "MOBILE_4G";
+            case MATCH_MOBILE_ALL:
+                return "MOBILE_ALL";
+            case MATCH_WIFI:
+                return "WIFI";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    public static final Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() {
+        public NetworkTemplate createFromParcel(Parcel in) {
+            return new NetworkTemplate(in);
+        }
+
+        public NetworkTemplate[] newArray(int size) {
+            return new NetworkTemplate[size];
+        }
+    };
+}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 3725fa6..cb47193 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -42,38 +42,12 @@
     public final static int UNSUPPORTED = -1;
 
     /**
-     * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
-     * networks together. Only uses statistics for requested IMSI.
+     * Special UID value used when collecting {@link NetworkStatsHistory} for
+     * removed applications.
      *
      * @hide
      */
-    public static final int TEMPLATE_MOBILE_ALL = 1;
-
-    /**
-     * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
-     * networks together that roughly meet a "3G" definition, or lower. Only
-     * uses statistics for requested IMSI.
-     *
-     * @hide
-     */
-    public static final int TEMPLATE_MOBILE_3G_LOWER = 2;
-
-    /**
-     * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
-     * networks together that meet a "4G" definition. Only uses statistics for
-     * requested IMSI.
-     *
-     * @hide
-     */
-    public static final int TEMPLATE_MOBILE_4G = 3;
-
-    /**
-     * Template to combine all {@link ConnectivityManager#TYPE_WIFI} style
-     * networks together.
-     *
-     * @hide
-     */
-    public static final int TEMPLATE_WIFI = 4;
+    public static final int UID_REMOVED = -4;
 
     /**
      * Snapshot of {@link NetworkStats} when the currently active profiling
@@ -182,17 +156,6 @@
         }
     }
 
-    /** {@hide} */
-    public static boolean isNetworkTemplateMobile(int networkTemplate) {
-        switch (networkTemplate) {
-            case TEMPLATE_MOBILE_3G_LOWER:
-            case TEMPLATE_MOBILE_4G:
-            case TEMPLATE_MOBILE_ALL:
-                return true;
-        }
-        return false;
-    }
-
     /**
      * Get the total number of packets transmitted through the mobile interface.
      *
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index 3fd26dd..5ade9eb 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -332,6 +332,28 @@
         return Uri.parse(new String(fullUri, Charsets.UTF_8));
     }
 
+    /**
+     * Creates an NDEF record of well known type URI.
+     * TODO: Make a public API
+     * @hide
+     */
+    public static NdefRecord createUri(Uri uri) {
+        String uriString = uri.toString();
+        byte prefix = 0x0;
+        for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
+            if (uriString.startsWith(URI_PREFIX_MAP[i])) {
+                prefix = (byte) i;
+                uriString = uriString.substring(URI_PREFIX_MAP[i].length());
+                break;
+            }
+        }
+        byte[] uriBytes = uriString.getBytes(Charsets.UTF_8);
+        byte[] recordBytes = new byte[uriBytes.length + 1];
+        recordBytes[0] = prefix;
+        System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
+        return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, new byte[0], recordBytes);
+    }
+
     private static byte[] concat(byte[]... arrays) {
         int length = 0;
         for (byte[] array : arrays) {
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 0a8c3ca..ba4804d 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -548,14 +548,6 @@
         }
 
         /**
-         * This download has been paused because requesting application has been
-         * blocked by {@link NetworkPolicyManager}.
-         *
-         * @hide
-         */
-        public static final int STATUS_PAUSED_BY_POLICY = 189;
-
-        /**
          * This download hasn't stated yet
          */
         public static final int STATUS_PENDING = 190;
@@ -704,6 +696,14 @@
         public static final int STATUS_TOO_MANY_REDIRECTS = 497;
 
         /**
+         * This download has failed because requesting application has been
+         * blocked by {@link NetworkPolicyManager}.
+         *
+         * @hide
+         */
+        public static final int STATUS_BLOCKED = 498;
+
+        /**
          * This download is visible but only shows in the notifications
          * while it's in progress.
          */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 01e028e7..603edf0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3800,6 +3800,8 @@
         public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration";
         /** {@hide} */
         public static final String NETSTATS_UID_MAX_HISTORY = "netstats_uid_max_history";
+        /** {@hide} */
+        public static final String NETSTATS_TAG_MAX_HISTORY = "netstats_tag_max_history";
 
         /**
          * @hide
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 164c657..0421205 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -306,6 +306,8 @@
      * <p><strong>Do not</strong> invoke this method from a drawing method
      * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
      * 
+     * <p>If an error occurs during the copy, an empty bitmap will be returned.</p>
+     * 
      * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
      *         texture is not available or the width &lt;= 0 or the height &lt;= 0
      * 
@@ -328,6 +330,8 @@
      * <p><strong>Do not</strong> invoke this method from a drawing method
      * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
      * 
+     * <p>If an error occurs during the copy, an empty bitmap will be returned.</p>
+     * 
      * @param width The width of the bitmap to create
      * @param height The height of the bitmap to create
      * 
@@ -354,6 +358,8 @@
      * <p><strong>Do not</strong> invoke this method from a drawing method
      * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
      * 
+     * <p>If an error occurs, the bitmap is left unchanged.</p>
+     * 
      * @param bitmap The bitmap to copy the content of the surface texture into,
      *               cannot be null, all configurations are supported
      * 
@@ -447,5 +453,6 @@
         public void onSurfaceTextureDestroyed(SurfaceTexture surface);
     }
 
-    private static native void nSetDefaultBufferSize(SurfaceTexture surfaceTexture, int width, int height);
+    private static native void nSetDefaultBufferSize(SurfaceTexture surfaceTexture,
+            int width, int height);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 888f0c0..46df88b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -345,7 +345,7 @@
  * 2 pixels to the right of the left edge. Padding can be set using the
  * {@link #setPadding(int, int, int, int)} method and queried by calling
  * {@link #getPaddingLeft()}, {@link #getPaddingTop()},
- * {@link #getPaddingRight()} and {@link #getPaddingBottom()}.
+ * {@link #getPaddingRight()}, {@link #getPaddingBottom()}.
  * </p>
  *
  * <p>
@@ -607,6 +607,8 @@
  * @attr ref android.R.styleable#View_paddingLeft
  * @attr ref android.R.styleable#View_paddingRight
  * @attr ref android.R.styleable#View_paddingTop
+ * @attr ref android.R.styleable#View_paddingStart
+ * @attr ref android.R.styleable#View_paddingEnd
  * @attr ref android.R.styleable#View_saveEnabled
  * @attr ref android.R.styleable#View_rotation
  * @attr ref android.R.styleable#View_rotationX
@@ -1734,11 +1736,20 @@
     static final int DRAG_HOVERED                 = 0x00000002;
 
     /**
-     * Indicates whether the view is drawn in right-to-left direction.
+     * Indicates whether the view layout direction has been resolved and drawn to the
+     * right-to-left direction.
      *
      * @hide
      */
-    static final int RESOLVED_LAYOUT_RTL          = 0x00000004;
+    static final int LAYOUT_DIRECTION_RESOLVED_RTL = 0x00000004;
+
+    /**
+     * Indicates whether the view layout direction has been resolved.
+     *
+     * @hide
+     */
+    static final int LAYOUT_DIRECTION_RESOLVED = 0x00000008;
+
 
     /* End of masks for mPrivateFlags2 */
 
@@ -2173,6 +2184,33 @@
     int mUserPaddingLeft;
 
     /**
+     * Cache the paddingTop set by the user to append to the scrollbar's size.
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    int mUserPaddingTop;
+
+    /**
+     * Cache if the user padding is relative.
+     *
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    boolean mUserPaddingRelative;
+
+    /**
+     * Cache the paddingStart set by the user to append to the scrollbar's size.
+     *
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    int mUserPaddingStart;
+
+    /**
+     * Cache the paddingEnd set by the user to append to the scrollbar's size.
+     *
+     */
+    @ViewDebug.ExportedProperty(category = "padding")
+    int mUserPaddingEnd;
+
+    /**
      * @hide
      */
     int mOldWidthMeasureSpec = Integer.MIN_VALUE;
@@ -2523,6 +2561,8 @@
         int topPadding = -1;
         int rightPadding = -1;
         int bottomPadding = -1;
+        int startPadding = -1;
+        int endPadding = -1;
 
         int padding = -1;
 
@@ -2568,6 +2608,12 @@
                 case com.android.internal.R.styleable.View_paddingBottom:
                     bottomPadding = a.getDimensionPixelSize(attr, -1);
                     break;
+                case com.android.internal.R.styleable.View_paddingStart:
+                    startPadding = a.getDimensionPixelSize(attr, -1);
+                    break;
+                case com.android.internal.R.styleable.View_paddingEnd:
+                    endPadding = a.getDimensionPixelSize(attr, -1);
+                    break;
                 case com.android.internal.R.styleable.View_scrollX:
                     x = a.getDimensionPixelOffset(attr, 0);
                     break;
@@ -2822,11 +2868,15 @@
             setBackgroundDrawable(background);
         }
 
+        mUserPaddingRelative = (startPadding >= 0 || endPadding >= 0);
+
         if (padding >= 0) {
             leftPadding = padding;
             topPadding = padding;
             rightPadding = padding;
             bottomPadding = padding;
+            startPadding = padding;
+            endPadding = padding;
         }
 
         // If the user specified the padding (either with android:padding or
@@ -2838,6 +2888,15 @@
                 rightPadding >= 0 ? rightPadding : mPaddingRight,
                 bottomPadding >= 0 ? bottomPadding : mPaddingBottom);
 
+        // Cache user padding as we cannot fully resolve padding here (we dont have yet the resolved
+        // layout direction). Those cached values will be used later during padding resolution.
+        mUserPaddingLeft = leftPadding;
+        mUserPaddingRight = rightPadding;
+        mUserPaddingStart = startPadding;
+        mUserPaddingEnd = endPadding;
+        mUserPaddingTop = topPadding;
+        mUserPaddingBottom = bottomPadding;
+
         if (viewFlagMasks != 0) {
             setFlags(viewFlagValues, viewFlagMasks);
         }
@@ -3059,7 +3118,7 @@
         }
 
         // Re-apply user/background padding so that scrollbar(s) get added
-        recomputePadding();
+        resolvePadding();
     }
 
     /**
@@ -3084,7 +3143,7 @@
         if (mVerticalScrollbarPosition != position) {
             mVerticalScrollbarPosition = position;
             computeOpaqueFlags();
-            recomputePadding();
+            resolvePadding();
         }
     }
 
@@ -4315,8 +4374,8 @@
         @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL,     to = "RESOLVED_DIRECTION_RTL")
     })
     public int getResolvedLayoutDirection() {
-        resolveLayoutDirection();
-        return ((mPrivateFlags2 & RESOLVED_LAYOUT_RTL) == RESOLVED_LAYOUT_RTL) ?
+        resolveLayoutDirectionIfNeeded();
+        return ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED_RTL) == LAYOUT_DIRECTION_RESOLVED_RTL) ?
                 LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
     }
 
@@ -8265,7 +8324,7 @@
         if (isHorizontalScrollBarEnabled() != horizontalScrollBarEnabled) {
             mViewFlags ^= SCROLLBARS_HORIZONTAL;
             computeOpaqueFlags();
-            recomputePadding();
+            resolvePadding();
         }
     }
 
@@ -8295,7 +8354,7 @@
         if (isVerticalScrollBarEnabled() != verticalScrollBarEnabled) {
             mViewFlags ^= SCROLLBARS_VERTICAL;
             computeOpaqueFlags();
-            recomputePadding();
+            resolvePadding();
         }
     }
 
@@ -8354,7 +8413,7 @@
         if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) {
             mViewFlags = (mViewFlags & ~SCROLLBARS_STYLE_MASK) | (style & SCROLLBARS_STYLE_MASK);
             computeOpaqueFlags();
-            recomputePadding();
+            resolvePadding();
         }
     }
 
@@ -8739,7 +8798,9 @@
             mPrivateFlags &= ~AWAKEN_SCROLL_BARS_ON_ATTACH;
         }
         jumpDrawablesToCurrentState();
-        resolveLayoutDirection();
+        resetLayoutDirectionResolution();
+        resolveLayoutDirectionIfNeeded();
+        resolvePadding();
         if (isFocused()) {
             InputMethodManager imm = InputMethodManager.peekInstance();
             imm.focusIn(this);
@@ -8747,31 +8808,91 @@
     }
 
     /**
-     * Resolving the layout direction. LTR is set initially.
-     * We are supposing here that the parent directionality will be resolved before its children.
+     * Resolve and cache the layout direction. LTR is set initially. This is implicitly supposing
+     * that the parent directionality can and will be resolved before its children.
      */
-    private void resolveLayoutDirection() {
-        mPrivateFlags2 &= ~RESOLVED_LAYOUT_RTL;
+    private void resolveLayoutDirectionIfNeeded() {
+        // Do not resolve if it is not needed
+        if ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED) == LAYOUT_DIRECTION_RESOLVED) return;
+
+        // Clear any previous layout direction resolution
+        mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_RTL;
+
+        // Set resolved depending on layout direction
         switch (getLayoutDirection()) {
             case LAYOUT_DIRECTION_INHERIT:
                 // If this is root view, no need to look at parent's layout dir.
                 if (mParent != null &&
                         mParent instanceof ViewGroup &&
                         ((ViewGroup) mParent).getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) {
-                    mPrivateFlags2 |= RESOLVED_LAYOUT_RTL;
+                    mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL;
                 }
                 break;
             case LAYOUT_DIRECTION_RTL:
-                mPrivateFlags2 |= RESOLVED_LAYOUT_RTL;
+                mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL;
                 break;
             case LAYOUT_DIRECTION_LOCALE:
                 if(isLayoutDirectionRtl(Locale.getDefault())) {
-                    mPrivateFlags2 |= RESOLVED_LAYOUT_RTL;
+                    mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL;
                 }
                 break;
             default:
                 // Nothing to do, LTR by default
         }
+
+        // Set to resolved
+        mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED;
+    }
+
+    private void resolvePadding() {
+        // If the user specified the absolute padding (either with android:padding or
+        // android:paddingLeft/Top/Right/Bottom), use this padding, otherwise
+        // use the default padding or the padding from the background drawable
+        // (stored at this point in mPadding*)
+        switch (getResolvedLayoutDirection()) {
+            case LAYOUT_DIRECTION_RTL:
+                // Start user padding override Right user padding. Otherwise, if Right user
+                // padding is not defined, use the default Right padding. If Right user padding
+                // is defined, just use it.
+                if (mUserPaddingStart >= 0) {
+                    mUserPaddingRight = mUserPaddingStart;
+                } else if (mUserPaddingRight < 0) {
+                    mUserPaddingRight = mPaddingRight;
+                }
+                if (mUserPaddingEnd >= 0) {
+                    mUserPaddingLeft = mUserPaddingEnd;
+                } else if (mUserPaddingLeft < 0) {
+                    mUserPaddingLeft = mPaddingLeft;
+                }
+                break;
+            case LAYOUT_DIRECTION_LTR:
+            default:
+                // Start user padding override Left user padding. Otherwise, if Left user
+                // padding is not defined, use the default left padding. If Left user padding
+                // is defined, just use it.
+                if (mUserPaddingStart >= 0) {
+                    mUserPaddingLeft = mUserPaddingStart;
+                } else if (mUserPaddingLeft < 0) {
+                    mUserPaddingLeft = mPaddingLeft;
+                }
+                if (mUserPaddingEnd >= 0) {
+                    mUserPaddingRight = mUserPaddingEnd;
+                } else if (mUserPaddingRight < 0) {
+                    mUserPaddingRight = mPaddingRight;
+                }
+        }
+
+        mPaddingTop = (mUserPaddingTop >= 0) ? mUserPaddingTop : mPaddingTop;
+        mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom;
+
+        recomputePadding();
+    }
+
+    /**
+     * Reset the resolved layout direction by clearing the corresponding flag
+     */
+    private void resetLayoutDirectionResolution() {
+        mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED;
     }
 
     /**
@@ -10745,7 +10866,14 @@
                 sThreadLocal.set(padding);
             }
             if (d.getPadding(padding)) {
-                setPadding(padding.left, padding.top, padding.right, padding.bottom);
+                switch (d.getResolvedLayoutDirectionSelf()) {
+                    case LAYOUT_DIRECTION_RTL:
+                        setPadding(padding.right, padding.top, padding.left, padding.bottom);
+                        break;
+                    case LAYOUT_DIRECTION_LTR:
+                    default:
+                        setPadding(padding.left, padding.top, padding.right, padding.bottom);
+                }
             }
 
             // Compare the minimum sizes of the old Drawable and the new.  If there isn't an old or
@@ -10831,6 +10959,8 @@
     public void setPadding(int left, int top, int right, int bottom) {
         boolean changed = false;
 
+        mUserPaddingRelative = false;
+
         mUserPaddingLeft = left;
         mUserPaddingRight = right;
         mUserPaddingBottom = bottom;
@@ -10840,11 +10970,16 @@
         // Common case is there are no scroll bars.
         if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) != 0) {
             if ((viewFlags & SCROLLBARS_VERTICAL) != 0) {
-                // TODO Determine what to do with SCROLLBAR_POSITION_DEFAULT based on RTL settings.
                 final int offset = (viewFlags & SCROLLBARS_INSET_MASK) == 0
                         ? 0 : getVerticalScrollbarWidth();
                 switch (mVerticalScrollbarPosition) {
                     case SCROLLBAR_POSITION_DEFAULT:
+                        if (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+                            left += offset;
+                        } else {
+                            right += offset;
+                        }
+                        break;
                     case SCROLLBAR_POSITION_RIGHT:
                         right += offset;
                         break;
@@ -10882,6 +11017,37 @@
     }
 
     /**
+     * Sets the relative padding. The view may add on the space required to display
+     * the scrollbars, depending on the style and visibility of the scrollbars.
+     * So the values returned from {@link #getPaddingStart}, {@link #getPaddingTop},
+     * {@link #getPaddingEnd} and {@link #getPaddingBottom} may be different
+     * from the values set in this call.
+     *
+     * @attr ref android.R.styleable#View_padding
+     * @attr ref android.R.styleable#View_paddingBottom
+     * @attr ref android.R.styleable#View_paddingStart
+     * @attr ref android.R.styleable#View_paddingEnd
+     * @attr ref android.R.styleable#View_paddingTop
+     * @param start the start padding in pixels
+     * @param top the top padding in pixels
+     * @param end the end padding in pixels
+     * @param bottom the bottom padding in pixels
+     *
+     * @hide
+     */
+    public void setPaddingRelative(int start, int top, int end, int bottom) {
+        mUserPaddingRelative = true;
+        switch(getResolvedLayoutDirection()) {
+            case LAYOUT_DIRECTION_RTL:
+                setPadding(end, top, start, bottom);
+                break;
+            case LAYOUT_DIRECTION_LTR:
+            default:
+                setPadding(start, top, end, bottom);
+        }
+    }
+
+    /**
      * Returns the top padding of this view.
      *
      * @return the top padding in pixels
@@ -10913,6 +11079,20 @@
     }
 
     /**
+     * Returns the start padding of this view. If there are inset and enabled
+     * scrollbars, this value may include the space required to display the
+     * scrollbars as well.
+     *
+     * @return the start padding in pixels
+     *
+     * @hide
+     */
+    public int getPaddingStart() {
+        return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+                mPaddingRight : mPaddingLeft;
+    }
+
+    /**
      * Returns the right padding of this view. If there are inset and enabled
      * scrollbars, this value may include the space required to display the
      * scrollbars as well.
@@ -10924,6 +11104,34 @@
     }
 
     /**
+     * Returns the end padding of this view. If there are inset and enabled
+     * scrollbars, this value may include the space required to display the
+     * scrollbars as well.
+     *
+     * @return the end padding in pixels
+     *
+     * @hide
+     */
+    public int getPaddingEnd() {
+        return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+                mPaddingLeft : mPaddingRight;
+    }
+
+    /**
+     * Return if the padding as been set thru relative values
+     * {@link #setPaddingRelative(int, int, int, int)} or thru
+     * @attr ref android.R.styleable#View_paddingStart or
+     * @attr ref android.R.styleable#View_paddingEnd
+     *
+     * @return true if the padding is relative or false if it is not.
+     *
+     * @hide
+     */
+    public boolean isPaddingRelative() {
+        return mUserPaddingRelative;
+    }
+
+    /**
      * Changes the selection state of this view. A view can be selected or not.
      * Note that selection is not the same as focus. Views are typically
      * selected in the context of an AdapterView like ListView or GridView;
diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java
index ba3ae58..afbedaf 100644
--- a/core/java/android/view/ViewAncestor.java
+++ b/core/java/android/view/ViewAncestor.java
@@ -4479,7 +4479,7 @@
                 ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList;
                 foundViews.clear();
 
-                View root = null;
+                View root;
                 if (accessibilityViewId != View.NO_ID) {
                     root = findViewByAccessibilityId(accessibilityViewId);
                 } else {
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 4a514bf..46e4398 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -765,9 +765,12 @@
         int computedWidth = getPaddingLeft() + mHorizontalAxis.getMin() + getPaddingRight();
         int computedHeight = getPaddingTop() + mVerticalAxis.getMin() + getPaddingBottom();
 
+        int measuredWidth = Math.max(computedWidth, getSuggestedMinimumWidth());
+        int measuredHeight = Math.max(computedHeight, getSuggestedMinimumHeight());
+
         setMeasuredDimension(
-                resolveSizeAndState(computedWidth, widthSpec, 0),
-                resolveSizeAndState(computedHeight, heightSpec, 0));
+                resolveSizeAndState(measuredWidth, widthSpec, 0),
+                resolveSizeAndState(measuredHeight, heightSpec, 0));
     }
 
     private int protect(int alignment) {
@@ -809,9 +812,9 @@
      and sizing to each child view and then placing it in its cell.
      */
     @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        int targetWidth = r - l;
-        int targetHeight = b - t;
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        int targetWidth = right - left;
+        int targetHeight = bottom - top;
 
         int paddingLeft = getPaddingLeft();
         int paddingTop = getPaddingTop();
@@ -2172,9 +2175,8 @@
          *
          * @param view              the view to which this alignment should be applied
          * @param viewSize          the measured size of the view
-         * @param measurementType   The type of measurement that should be made. This feature
-         *                          is currently unused as GridLayout only supports one
-         *                          type of measurement: {@link View#measure(int, int)}.
+         * @param measurementType   This parameter is currently unused as GridLayout only supports
+         *                          one type of measurement: {@link View#measure(int, int)}.
          *
          * @return                  the alignment value
          */
@@ -2190,9 +2192,8 @@
          * @param view              the view to which this alignment should be applied
          * @param viewSize          the measured size of the view
          * @param cellSize          the size of the cell into which this view will be placed
-         * @param measurementType   The type of measurement that should be made. This feature
-         *                          is currently unused as GridLayout only supports one
-         *                          type of measurement: {@link View#measure(int, int)}.
+         * @param measurementType   This parameter is currently unused as GridLayout only supports
+         *                          one type of measurement: {@link View#measure(int, int)}.
          *
          * @return                  the aligned size
          */
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 9d8d361..c475eff 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -96,6 +96,8 @@
     private LinearLayout mTitleLayout;
     private TextView mTitleView;
     private TextView mSubtitleView;
+    private View mTitleUpView;
+
     private Spinner mSpinner;
     private LinearLayout mListNavLayout;
     private ScrollingTabContainerView mTabScrollView;
@@ -152,6 +154,16 @@
         }
     };
 
+    private final OnClickListener mUpClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            Context context = getContext();
+            if (context instanceof Activity) {
+                Activity activity = (Activity) context;
+                activity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem);
+            }
+        }
+    };
+
     public ActionBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -230,15 +242,7 @@
         a.recycle();
         
         mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
-        mHomeLayout.setOnClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                Context context = getContext();
-                if (context instanceof Activity) {
-                    Activity activity = (Activity) context;
-                    activity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem);
-                }
-            }
-        });
+        mHomeLayout.setOnClickListener(mUpClickListener);
         mHomeLayout.setClickable(true);
         mHomeLayout.setFocusable(true);
     }
@@ -438,7 +442,8 @@
         }
 
         if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
-            final int vis = (options & ActionBar.DISPLAY_SHOW_HOME) != 0 ? VISIBLE : GONE;
+            final boolean showHome = (options & ActionBar.DISPLAY_SHOW_HOME) != 0;
+            final int vis = showHome ? VISIBLE : GONE;
             mHomeLayout.setVisibility(vis);
 
             if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
@@ -458,6 +463,14 @@
                 }
             }
 
+            if (mTitleLayout != null && (flagsChanged &
+                    (ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME)) != 0) {
+                final boolean homeAsUp = (options & ActionBar.DISPLAY_HOME_AS_UP) != 0;
+                final boolean titleUp = homeAsUp && !showHome;
+                mTitleUpView.setVisibility(titleUp ? VISIBLE : GONE);
+                mTitleLayout.setEnabled(titleUp);
+            }
+
             if ((flagsChanged & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
                 if ((options & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
                     addView(mCustomNavView);
@@ -637,24 +650,35 @@
     }
     
     private void initTitle() {
-        LayoutInflater inflater = LayoutInflater.from(getContext());
-        mTitleLayout = (LinearLayout) inflater.inflate(R.layout.action_bar_title_item, null);
-        mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
-        mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
+        if (mTitleLayout == null) {
+            LayoutInflater inflater = LayoutInflater.from(getContext());
+            mTitleLayout = (LinearLayout) inflater.inflate(R.layout.action_bar_title_item, null);
+            mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
+            mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
+            mTitleUpView = (View) mTitleLayout.findViewById(R.id.up);
 
-        if (mTitleStyleRes != 0) {
-            mTitleView.setTextAppearance(mContext, mTitleStyleRes);
-        }
-        if (mTitle != null) {
-            mTitleView.setText(mTitle);
-        }
+            mTitleLayout.setOnClickListener(mUpClickListener);
 
-        if (mSubtitleStyleRes != 0) {
-            mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
-        }
-        if (mSubtitle != null) {
-            mSubtitleView.setText(mSubtitle);
-            mSubtitleView.setVisibility(VISIBLE);
+            if (mTitleStyleRes != 0) {
+                mTitleView.setTextAppearance(mContext, mTitleStyleRes);
+            }
+            if (mTitle != null) {
+                mTitleView.setText(mTitle);
+            }
+
+            if (mSubtitleStyleRes != 0) {
+                mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
+            }
+            if (mSubtitle != null) {
+                mSubtitleView.setText(mSubtitle);
+                mSubtitleView.setVisibility(VISIBLE);
+            }
+
+            final boolean homeAsUp = (mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0;
+            final boolean titleUp = homeAsUp &&
+                    (mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) == 0;
+            mTitleUpView.setVisibility(titleUp ? VISIBLE : GONE);
+            mTitleLayout.setEnabled(titleUp);
         }
 
         addView(mTitleLayout);
@@ -734,7 +758,7 @@
 
         if (mExpandedActionView == null) {
             boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
-            (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
+                    (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
             if (showTitle) {
                 availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
                 leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth());
@@ -872,7 +896,7 @@
 
         if (mExpandedActionView == null) {
             final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
-            (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
+                    (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
             if (showTitle) {
                 x += positionChild(mTitleLayout, x, y, contentHeight);
             }
@@ -1193,7 +1217,7 @@
                 addView(mExpandedHomeLayout);
             }
             mHomeLayout.setVisibility(GONE);
-            mTitleLayout.setVisibility(GONE);
+            if (mTitleLayout != null) mTitleLayout.setVisibility(GONE);
             if (mTabScrollView != null) mTabScrollView.setVisibility(GONE);
             if (mSpinner != null) mSpinner.setVisibility(GONE);
             if (mCustomNavView != null) mCustomNavView.setVisibility(GONE);
@@ -1210,7 +1234,11 @@
                 mHomeLayout.setVisibility(VISIBLE);
             }
             if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
-                mTitleLayout.setVisibility(VISIBLE);
+                if (mTitleLayout == null) {
+                    initTitle();
+                } else {
+                    mTitleLayout.setVisibility(VISIBLE);
+                }
             }
             if (mTabScrollView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
                 mTabScrollView.setVisibility(VISIBLE);
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index fd49ae3..cbb110a 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -618,178 +618,20 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent motionEvent) {
+    public boolean onTouchEvent(MotionEvent event) {
         if (!mInputEnabled || !isEnabled()) {
             return false;
         }
 
-        final float x = motionEvent.getX();
-        final float y = motionEvent.getY();
-        Cell hitCell;
-        switch(motionEvent.getAction()) {
+        switch(event.getAction()) {
             case MotionEvent.ACTION_DOWN:
-                resetPattern();
-                hitCell = detectAndAddHit(x, y);
-                if (hitCell != null && mOnPatternListener != null) {
-                    mPatternInProgress = true;
-                    mPatternDisplayMode = DisplayMode.Correct;
-                    mOnPatternListener.onPatternStart();
-                } else if (mOnPatternListener != null) {
-                    mPatternInProgress = false;
-                    mOnPatternListener.onPatternCleared();
-                }
-                if (hitCell != null) {
-                    final float startX = getCenterXForColumn(hitCell.column);
-                    final float startY = getCenterYForRow(hitCell.row);
-
-                    final float widthOffset = mSquareWidth / 2f;
-                    final float heightOffset = mSquareHeight / 2f;
-
-                    invalidate((int) (startX - widthOffset), (int) (startY - heightOffset),
-                            (int) (startX + widthOffset), (int) (startY + heightOffset));
-                }
-                mInProgressX = x;
-                mInProgressY = y;
-                if (PROFILE_DRAWING) {
-                    if (!mDrawingProfilingStarted) {
-                        Debug.startMethodTracing("LockPatternDrawing");
-                        mDrawingProfilingStarted = true;
-                    }
-                }
+                handleActionDown(event);
                 return true;
             case MotionEvent.ACTION_UP:
-                // report pattern detected
-                if (!mPattern.isEmpty() && mOnPatternListener != null) {
-                    mPatternInProgress = false;
-                    mOnPatternListener.onPatternDetected(mPattern);
-                    invalidate();
-                }
-                if (PROFILE_DRAWING) {
-                    if (mDrawingProfilingStarted) {
-                        Debug.stopMethodTracing();
-                        mDrawingProfilingStarted = false;
-                    }
-                }
+                handleActionUp(event);
                 return true;
             case MotionEvent.ACTION_MOVE:
-                final int patternSizePreHitDetect = mPattern.size();
-                hitCell = detectAndAddHit(x, y);
-                final int patternSize = mPattern.size();
-                if (hitCell != null && (mOnPatternListener != null) && (patternSize == 1)) {
-                    mPatternInProgress = true;
-                    mOnPatternListener.onPatternStart();
-                }
-                // note current x and y for rubber banding of in progress
-                // patterns
-                final float dx = Math.abs(x - mInProgressX);
-                final float dy = Math.abs(y - mInProgressY);
-                if (dx + dy > mSquareWidth * 0.01f) {
-                    float oldX = mInProgressX;
-                    float oldY = mInProgressY;
-
-                    mInProgressX = x;
-                    mInProgressY = y;
-
-                    if (mPatternInProgress && patternSize > 0) {
-                        final ArrayList<Cell> pattern = mPattern;
-                        final float radius = mSquareWidth * mDiameterFactor * 0.5f;
-
-                        final Cell lastCell = pattern.get(patternSize - 1);
-
-                        float startX = getCenterXForColumn(lastCell.column);
-                        float startY = getCenterYForRow(lastCell.row);
-
-                        float left;
-                        float top;
-                        float right;
-                        float bottom;
-
-                        final Rect invalidateRect = mInvalidate;
-
-                        if (startX < x) {
-                            left = startX;
-                            right = x;
-                        } else {
-                            left = x;
-                            right = startX;
-                        }
-
-                        if (startY < y) {
-                            top = startY;
-                            bottom = y;
-                        } else {
-                            top = y;
-                            bottom = startY;
-                        }
-
-                        // Invalidate between the pattern's last cell and the current location
-                        invalidateRect.set((int) (left - radius), (int) (top - radius),
-                                (int) (right + radius), (int) (bottom + radius));
-
-                        if (startX < oldX) {
-                            left = startX;
-                            right = oldX;
-                        } else {
-                            left = oldX;
-                            right = startX;
-                        }
-
-                        if (startY < oldY) {
-                            top = startY;
-                            bottom = oldY;
-                        } else {
-                            top = oldY;
-                            bottom = startY;
-                        }
-
-                        // Invalidate between the pattern's last cell and the previous location
-                        invalidateRect.union((int) (left - radius), (int) (top - radius),
-                                (int) (right + radius), (int) (bottom + radius));
-
-                        // Invalidate between the pattern's new cell and the pattern's previous cell
-                        if (hitCell != null) {
-                            startX = getCenterXForColumn(hitCell.column);
-                            startY = getCenterYForRow(hitCell.row);
-
-                            if (patternSize >= 2) {
-                                // (re-using hitcell for old cell)
-                                hitCell = pattern.get(patternSize - 1 - (patternSize - patternSizePreHitDetect));
-                                oldX = getCenterXForColumn(hitCell.column);
-                                oldY = getCenterYForRow(hitCell.row);
-
-                                if (startX < oldX) {
-                                    left = startX;
-                                    right = oldX;
-                                } else {
-                                    left = oldX;
-                                    right = startX;
-                                }
-
-                                if (startY < oldY) {
-                                    top = startY;
-                                    bottom = oldY;
-                                } else {
-                                    top = oldY;
-                                    bottom = startY;
-                                }
-                            } else {
-                                left = right = startX;
-                                top = bottom = startY;
-                            }
-
-                            final float widthOffset = mSquareWidth / 2f;
-                            final float heightOffset = mSquareHeight / 2f;
-
-                            invalidateRect.set((int) (left - widthOffset),
-                                    (int) (top - heightOffset), (int) (right + widthOffset),
-                                    (int) (bottom + heightOffset));
-                        }
-
-                        invalidate(invalidateRect);
-                    } else {
-                        invalidate();
-                    }
-                }
+                handleActionMove(event);
                 return true;
             case MotionEvent.ACTION_CANCEL:
                 resetPattern();
@@ -808,6 +650,181 @@
         return false;
     }
 
+    private void handleActionMove(MotionEvent event) {
+        // Handle all recent motion events so we don't skip any cells even when the device
+        // is busy...
+        final int historySize = event.getHistorySize();
+        for (int i = 0; i < historySize + 1; i++) {
+            final float x = i < historySize ? event.getHistoricalX(i) : event.getX();
+            final float y = i < historySize ? event.getHistoricalY(i) : event.getY();
+            final int patternSizePreHitDetect = mPattern.size();
+            Cell hitCell = detectAndAddHit(x, y);
+            final int patternSize = mPattern.size();
+            if (hitCell != null && (mOnPatternListener != null) && (patternSize == 1)) {
+                mPatternInProgress = true;
+                mOnPatternListener.onPatternStart();
+            }
+            // note current x and y for rubber banding of in progress patterns
+            final float dx = Math.abs(x - mInProgressX);
+            final float dy = Math.abs(y - mInProgressY);
+            if (dx + dy > mSquareWidth * 0.01f) {
+                float oldX = mInProgressX;
+                float oldY = mInProgressY;
+
+                mInProgressX = x;
+                mInProgressY = y;
+
+                if (mPatternInProgress && patternSize > 0) {
+                    final ArrayList<Cell> pattern = mPattern;
+                    final float radius = mSquareWidth * mDiameterFactor * 0.5f;
+
+                    final Cell lastCell = pattern.get(patternSize - 1);
+
+                    float startX = getCenterXForColumn(lastCell.column);
+                    float startY = getCenterYForRow(lastCell.row);
+
+                    float left;
+                    float top;
+                    float right;
+                    float bottom;
+
+                    final Rect invalidateRect = mInvalidate;
+
+                    if (startX < x) {
+                        left = startX;
+                        right = x;
+                    } else {
+                        left = x;
+                        right = startX;
+                    }
+
+                    if (startY < y) {
+                        top = startY;
+                        bottom = y;
+                    } else {
+                        top = y;
+                        bottom = startY;
+                    }
+
+                    // Invalidate between the pattern's last cell and the current location
+                    invalidateRect.set((int) (left - radius), (int) (top - radius),
+                            (int) (right + radius), (int) (bottom + radius));
+
+                    if (startX < oldX) {
+                        left = startX;
+                        right = oldX;
+                    } else {
+                        left = oldX;
+                        right = startX;
+                    }
+
+                    if (startY < oldY) {
+                        top = startY;
+                        bottom = oldY;
+                    } else {
+                        top = oldY;
+                        bottom = startY;
+                    }
+
+                    // Invalidate between the pattern's last cell and the previous location
+                    invalidateRect.union((int) (left - radius), (int) (top - radius),
+                            (int) (right + radius), (int) (bottom + radius));
+
+                    // Invalidate between the pattern's new cell and the pattern's previous cell
+                    if (hitCell != null) {
+                        startX = getCenterXForColumn(hitCell.column);
+                        startY = getCenterYForRow(hitCell.row);
+
+                        if (patternSize >= 2) {
+                            // (re-using hitcell for old cell)
+                            hitCell = pattern.get(patternSize - 1 - (patternSize - patternSizePreHitDetect));
+                            oldX = getCenterXForColumn(hitCell.column);
+                            oldY = getCenterYForRow(hitCell.row);
+
+                            if (startX < oldX) {
+                                left = startX;
+                                right = oldX;
+                            } else {
+                                left = oldX;
+                                right = startX;
+                            }
+
+                            if (startY < oldY) {
+                                top = startY;
+                                bottom = oldY;
+                            } else {
+                                top = oldY;
+                                bottom = startY;
+                            }
+                        } else {
+                            left = right = startX;
+                            top = bottom = startY;
+                        }
+
+                        final float widthOffset = mSquareWidth / 2f;
+                        final float heightOffset = mSquareHeight / 2f;
+
+                        invalidateRect.set((int) (left - widthOffset),
+                                (int) (top - heightOffset), (int) (right + widthOffset),
+                                (int) (bottom + heightOffset));
+                    }
+
+                    invalidate(invalidateRect);
+                } else {
+                    invalidate();
+                }
+            }
+        }
+    }
+
+    private void handleActionUp(MotionEvent event) {
+        // report pattern detected
+        if (!mPattern.isEmpty() && mOnPatternListener != null) {
+            mPatternInProgress = false;
+            mOnPatternListener.onPatternDetected(mPattern);
+            invalidate();
+        }
+        if (PROFILE_DRAWING) {
+            if (mDrawingProfilingStarted) {
+                Debug.stopMethodTracing();
+                mDrawingProfilingStarted = false;
+            }
+        }
+    }
+
+    private void handleActionDown(MotionEvent event) {
+        resetPattern();
+        final float x = event.getX();
+        final float y = event.getY();
+        final Cell hitCell = detectAndAddHit(x, y);
+        if (hitCell != null && mOnPatternListener != null) {
+            mPatternInProgress = true;
+            mPatternDisplayMode = DisplayMode.Correct;
+            mOnPatternListener.onPatternStart();
+        } else if (mOnPatternListener != null) {
+            mPatternInProgress = false;
+            mOnPatternListener.onPatternCleared();
+        }
+        if (hitCell != null) {
+            final float startX = getCenterXForColumn(hitCell.column);
+            final float startY = getCenterYForRow(hitCell.row);
+
+            final float widthOffset = mSquareWidth / 2f;
+            final float heightOffset = mSquareHeight / 2f;
+
+            invalidate((int) (startX - widthOffset), (int) (startY - heightOffset),
+                    (int) (startX + widthOffset), (int) (startY + heightOffset));
+        }
+        mInProgressX = x;
+        mInProgressY = y;
+        if (PROFILE_DRAWING) {
+            if (!mDrawingProfilingStarted) {
+                Debug.startMethodTracing("LockPatternDrawing");
+                mDrawingProfilingStarted = true;
+            }
+        }
+    }
+
     private float getCenterXForColumn(int column) {
         return mPaddingLeft + column * mSquareWidth + mSquareWidth / 2f;
     }
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index 3f922f6..0d28cb1 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -91,6 +91,8 @@
     virtual void onFrameAvailable();
 
 private:
+    static JNIEnv* getJNIEnv();
+
     jobject mWeakThiz;
     jclass mClazz;
 };
@@ -101,17 +103,37 @@
     mClazz((jclass)env->NewGlobalRef(clazz))
 {}
 
+JNIEnv* JNISurfaceTextureContext::getJNIEnv() {
+    JNIEnv* env;
+    JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
+    JavaVM* vm = AndroidRuntime::getJavaVM();
+    int result = vm->AttachCurrentThread(&env, (void*) &args);
+    if (result != JNI_OK) {
+        LOGE("thread attach failed: %#x", result);
+        return NULL;
+    }
+    return env;
+}
+
 JNISurfaceTextureContext::~JNISurfaceTextureContext()
 {
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-    env->DeleteGlobalRef(mWeakThiz);
-    env->DeleteGlobalRef(mClazz);
+    JNIEnv* env = getJNIEnv();
+    if (env != NULL) {
+        env->DeleteGlobalRef(mWeakThiz);
+        env->DeleteGlobalRef(mClazz);
+    } else {
+        LOGW("leaking JNI object references");
+    }
 }
 
 void JNISurfaceTextureContext::onFrameAvailable()
 {
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-    env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
+    JNIEnv *env = getJNIEnv();
+    if (env != NULL) {
+        env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
+    } else {
+        LOGW("onFrameAvailable event will not posted");
+    }
 }
 
 // ----------------------------------------------------------------------------
diff --git a/core/res/res/layout/action_bar_title_item.xml b/core/res/res/layout/action_bar_title_item.xml
index d8b729d..e803b26 100644
--- a/core/res/res/layout/action_bar_title_item.xml
+++ b/core/res/res/layout/action_bar_title_item.xml
@@ -17,17 +17,32 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
-              android:orientation="vertical"
-              android:paddingRight="32dip" >
-    <TextView android:id="@+id/action_bar_title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        android:ellipsize="end" />
-    <TextView android:id="@+id/action_bar_subtitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        android:ellipsize="end"
-        android:visibility="gone" />
+              android:orientation="horizontal"
+              android:paddingRight="16dip"
+              android:background="?android:attr/selectableItemBackground"
+              android:enabled="false">
+
+    <ImageView android:id="@android:id/up"
+               android:src="?android:attr/homeAsUpIndicator"
+               android:layout_gravity="center_vertical|left"
+               android:visibility="gone"
+               android:layout_width="wrap_content"
+               android:layout_height="wrap_content" />
+
+    <LinearLayout android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_gravity="center_vertical|left"
+                  android:orientation="vertical">
+        <TextView android:id="@+id/action_bar_title"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:ellipsize="end" />
+        <TextView android:id="@+id/action_bar_subtitle"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:ellipsize="end"
+                  android:visibility="gone" />
+    </LinearLayout>
 </LinearLayout>
diff --git a/core/res/res/layout/alert_dialog_holo.xml b/core/res/res/layout/alert_dialog_holo.xml
index 1a3573e..2185467 100644
--- a/core/res/res/layout/alert_dialog_holo.xml
+++ b/core/res/res/layout/alert_dialog_holo.xml
@@ -82,7 +82,9 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:paddingLeft="16dip"
-                android:paddingRight="16dip" />
+                android:paddingRight="16dip"
+                android:paddingTop="8dip"
+                android:paddingBottom="8dip"/>
         </ScrollView>
     </LinearLayout>
 
diff --git a/core/res/res/layout/keyguard_screen_status_land.xml b/core/res/res/layout/keyguard_screen_status_land.xml
index 259a3af..8a02e1f 100644
--- a/core/res/res/layout/keyguard_screen_status_land.xml
+++ b/core/res/res/layout/keyguard_screen_status_land.xml
@@ -133,16 +133,4 @@
         android:textAppearance="?android:attr/textAppearanceMedium"
         />
 
-    <TextView
-        android:id="@+id/propertyOf"
-        android:lineSpacingExtra="8dip"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textSize="17sp"
-        android:layout_marginTop="20dip"
-        android:singleLine="false"
-        android:textColor="@color/lockscreen_owner_info"
-        android:visibility="gone"
-        />
 </LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_status_port.xml b/core/res/res/layout/keyguard_screen_status_port.xml
index 680c073..1e87fb3 100644
--- a/core/res/res/layout/keyguard_screen_status_port.xml
+++ b/core/res/res/layout/keyguard_screen_status_port.xml
@@ -130,16 +130,4 @@
         android:textAppearance="?android:attr/textAppearanceMedium"
         />
 
-    <TextView
-        android:id="@+id/propertyOf"
-        android:lineSpacingExtra="8dip"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="20dip"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textSize="17sp"
-        android:singleLine="false"
-        android:visibility="gone"
-        android:textColor="@color/lockscreen_owner_info"
-        />
 </LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index 0132b6c..4ffa3405 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -63,10 +63,8 @@
 
     <LinearLayout
         android:orientation="horizontal"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="0dip"
-        android:layout_marginLeft="12dip"
         android:layout_gravity="right">
 
         <TextView
@@ -114,8 +112,8 @@
 
     <com.android.internal.widget.LockPatternView
         android:id="@+id/lockPattern"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_width="300dip"
+        android:layout_height="300dip"
         android:layout_rowWeight="1"
         android:layout_marginTop="8dip"
         android:layout_marginRight="8dip"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a59af1a..989adbd 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1661,6 +1661,10 @@
         <attr name="paddingRight" format="dimension" />
         <!-- Sets the padding, in pixels, of the bottom edge; see {@link android.R.attr#padding}. -->
         <attr name="paddingBottom" format="dimension" />
+        <!-- Sets the padding, in pixels, of the start edge; see {@link android.R.attr#padding}. -->
+        <attr name="paddingStart" format="dimension" />
+        <!-- Sets the padding, in pixels, of the end edge; see {@link android.R.attr#padding}. -->
+        <attr name="paddingEnd" format="dimension" />
 
         <!-- Boolean that controls whether a view can take focus.  By default the user can not
              move focus to a view; by setting this attribute to true the view is
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index e02496c..580c204 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1775,4 +1775,7 @@
   <public type="integer" name="status_bar_notification_info_maxnum" />
   <public type="string" name="status_bar_notification_info_overflow" />
 
+  <public type="attr" name="paddingStart"/>
+  <public type="attr" name="paddingEnd"/>
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d9e7dac..b5f4084 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2608,6 +2608,11 @@
     <!-- USB_STORAGE_ERROR dialog  ok button-->
     <string name="dlg_ok">OK</string>
 
+    <!-- USB_PREFERENCES: When the user connects the phone to a computer via USB, we show a notification asking if he wants to share files across.  This is the title -->
+    <string name="usb_preferences_notification_title">USB connected</string>
+    <!-- See USB_PREFERENCES. This is the message. -->
+    <string name="usb_preferece_notification_message">Select to configure USB file transfer.</string>
+
     <!-- External media format dialog strings -->
     <!-- This is the label for the activity, and should never be visible to the user. -->
     <!-- See EXTMEDIA_FORMAT.  EXTMEDIA_FORMAT_DIALOG:  After the user selects the notification, a dialog is shown asking if he wants to format the SD card.  This is the title. [CHAR LIMIT=20] -->
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 5250a7c..3cb64c7 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.net.NetworkStats.TAG_NONE;
+
 import android.test.suitebuilder.annotation.SmallTest;
 
 import junit.framework.TestCase;
@@ -29,14 +31,14 @@
 
     public void testFindIndex() throws Exception {
         final NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addEntry(TEST_IFACE, 100, 1024, 0)
-                .addEntry(TEST_IFACE, 101, 0, 1024)
-                .addEntry(TEST_IFACE, 102, 1024, 1024);
+                .addEntry(TEST_IFACE, 100, TAG_NONE, 1024L, 0L)
+                .addEntry(TEST_IFACE, 101, TAG_NONE, 0L, 1024L)
+                .addEntry(TEST_IFACE, 102, TAG_NONE, 1024L, 1024L);
 
-        assertEquals(2, stats.findIndex(TEST_IFACE, 102));
-        assertEquals(2, stats.findIndex(TEST_IFACE, 102));
-        assertEquals(0, stats.findIndex(TEST_IFACE, 100));
-        assertEquals(-1, stats.findIndex(TEST_IFACE, 6));
+        assertEquals(2, stats.findIndex(TEST_IFACE, 102, TAG_NONE));
+        assertEquals(2, stats.findIndex(TEST_IFACE, 102, TAG_NONE));
+        assertEquals(0, stats.findIndex(TEST_IFACE, 100, TAG_NONE));
+        assertEquals(-1, stats.findIndex(TEST_IFACE, 6, TAG_NONE));
     }
 
     public void testAddEntryGrow() throws Exception {
@@ -45,15 +47,15 @@
         assertEquals(0, stats.size);
         assertEquals(2, stats.iface.length);
 
-        stats.addEntry(TEST_IFACE, TEST_UID, 1L, 2L);
-        stats.addEntry(TEST_IFACE, TEST_UID, 2L, 2L);
+        stats.addEntry(TEST_IFACE, TEST_UID, TAG_NONE, 1L, 2L);
+        stats.addEntry(TEST_IFACE, TEST_UID, TAG_NONE, 2L, 2L);
 
         assertEquals(2, stats.size);
         assertEquals(2, stats.iface.length);
 
-        stats.addEntry(TEST_IFACE, TEST_UID, 3L, 4L);
-        stats.addEntry(TEST_IFACE, TEST_UID, 4L, 4L);
-        stats.addEntry(TEST_IFACE, TEST_UID, 5L, 5L);
+        stats.addEntry(TEST_IFACE, TEST_UID, TAG_NONE, 3L, 4L);
+        stats.addEntry(TEST_IFACE, TEST_UID, TAG_NONE, 4L, 4L);
+        stats.addEntry(TEST_IFACE, TEST_UID, TAG_NONE, 5L, 5L);
 
         assertEquals(5, stats.size);
         assertTrue(stats.iface.length >= 5);
@@ -65,14 +67,31 @@
         assertEquals(5L, stats.rx[4]);
     }
 
+    public void testCombineExisting() throws Exception {
+        final NetworkStats stats = new NetworkStats(TEST_START, 10);
+
+        stats.addEntry(TEST_IFACE, 1001, TAG_NONE, 512L, 256L);
+        stats.addEntry(TEST_IFACE, 1001, 0xff, 128L, 128L);
+        stats.combineEntry(TEST_IFACE, 1001, TAG_NONE, -128L, -128L);
+
+        assertStatsEntry(stats, 0, TEST_IFACE, 1001, TAG_NONE, 384L, 128L);
+        assertStatsEntry(stats, 1, TEST_IFACE, 1001, 0xff, 128L, 128L);
+
+        // now try combining that should create row
+        stats.combineEntry(TEST_IFACE, 5005, TAG_NONE, 128L, 128L);
+        assertStatsEntry(stats, 2, TEST_IFACE, 5005, TAG_NONE, 128L, 128L);
+        stats.combineEntry(TEST_IFACE, 5005, TAG_NONE, 128L, 128L);
+        assertStatsEntry(stats, 2, TEST_IFACE, 5005, TAG_NONE, 256L, 256L);
+    }
+
     public void testSubtractIdenticalData() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addEntry(TEST_IFACE, 100, 1024, 0)
-                .addEntry(TEST_IFACE, 101, 0, 1024);
+                .addEntry(TEST_IFACE, 100, TAG_NONE, 1024L, 0L)
+                .addEntry(TEST_IFACE, 101, TAG_NONE, 0L, 1024L);
 
         final NetworkStats after = new NetworkStats(TEST_START, 2)
-                .addEntry(TEST_IFACE, 100, 1024, 0)
-                .addEntry(TEST_IFACE, 101, 0, 1024);
+                .addEntry(TEST_IFACE, 100, TAG_NONE, 1024L, 0L)
+                .addEntry(TEST_IFACE, 101, TAG_NONE, 0L, 1024L);
 
         final NetworkStats result = after.subtract(before);
 
@@ -85,12 +104,12 @@
 
     public void testSubtractIdenticalRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addEntry(TEST_IFACE, 100, 1024, 0)
-                .addEntry(TEST_IFACE, 101, 0, 1024);
+                .addEntry(TEST_IFACE, 100, TAG_NONE, 1024L, 0L)
+                .addEntry(TEST_IFACE, 101, TAG_NONE, 0L, 1024L);
 
         final NetworkStats after = new NetworkStats(TEST_START, 2)
-                .addEntry(TEST_IFACE, 100, 1025, 2)
-                .addEntry(TEST_IFACE, 101, 3, 1028);
+                .addEntry(TEST_IFACE, 100, TAG_NONE, 1025L, 2L)
+                .addEntry(TEST_IFACE, 101, TAG_NONE, 3L, 1028L);
 
         final NetworkStats result = after.subtract(before);
 
@@ -103,13 +122,13 @@
 
     public void testSubtractNewRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addEntry(TEST_IFACE, 100, 1024, 0)
-                .addEntry(TEST_IFACE, 101, 0, 1024);
+                .addEntry(TEST_IFACE, 100, TAG_NONE, 1024L, 0L)
+                .addEntry(TEST_IFACE, 101, TAG_NONE, 0L, 1024L);
 
         final NetworkStats after = new NetworkStats(TEST_START, 3)
-                .addEntry(TEST_IFACE, 100, 1024, 0)
-                .addEntry(TEST_IFACE, 101, 0, 1024)
-                .addEntry(TEST_IFACE, 102, 1024, 1024);
+                .addEntry(TEST_IFACE, 100, TAG_NONE, 1024L, 0L)
+                .addEntry(TEST_IFACE, 101, TAG_NONE, 0L, 1024L)
+                .addEntry(TEST_IFACE, 102, TAG_NONE, 1024L, 1024L);
 
         final NetworkStats result = after.subtract(before);
 
@@ -122,4 +141,13 @@
         assertEquals(1024, result.tx[2]);
     }
 
+    private static void assertStatsEntry(
+            NetworkStats stats, int i, String iface, int uid, int tag, long rx, long tx) {
+        assertEquals(iface, stats.iface[i]);
+        assertEquals(uid, stats.uid[i]);
+        assertEquals(tag, stats.tag[i]);
+        assertEquals(rx, stats.rx[i]);
+        assertEquals(tx, stats.tx[i]);
+    }
+
 }
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index eeab9b4..e900584 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -79,7 +79,7 @@
 
     /**
      * GRAPHICS_TEXTURE The allcation will be used as a texture
-     * source by one or more graphcics programs.
+     * source by one or more graphics programs.
      *
      */
     public static final int USAGE_GRAPHICS_TEXTURE = 0x0002;
diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java
index 5a72dbe..f844331 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -383,6 +383,41 @@
         return rs.mElement_FLOAT_4;
     }
 
+    public static Element F64_2(RenderScript rs) {
+        if(rs.mElement_DOUBLE_2 == null) {
+            rs.mElement_DOUBLE_2 = createVector(rs, DataType.FLOAT_64, 2);
+        }
+        return rs.mElement_DOUBLE_2;
+    }
+
+    public static Element F64_3(RenderScript rs) {
+        if(rs.mElement_DOUBLE_3 == null) {
+            rs.mElement_DOUBLE_3 = createVector(rs, DataType.FLOAT_64, 3);
+        }
+        return rs.mElement_DOUBLE_3;
+    }
+
+    public static Element F64_4(RenderScript rs) {
+        if(rs.mElement_DOUBLE_4 == null) {
+            rs.mElement_DOUBLE_4 = createVector(rs, DataType.FLOAT_64, 4);
+        }
+        return rs.mElement_DOUBLE_4;
+    }
+
+    public static Element U8_2(RenderScript rs) {
+        if(rs.mElement_UCHAR_2 == null) {
+            rs.mElement_UCHAR_2 = createVector(rs, DataType.UNSIGNED_8, 2);
+        }
+        return rs.mElement_UCHAR_2;
+    }
+
+    public static Element U8_3(RenderScript rs) {
+        if(rs.mElement_UCHAR_3 == null) {
+            rs.mElement_UCHAR_3 = createVector(rs, DataType.UNSIGNED_8, 3);
+        }
+        return rs.mElement_UCHAR_3;
+    }
+
     public static Element U8_4(RenderScript rs) {
         if(rs.mElement_UCHAR_4 == null) {
             rs.mElement_UCHAR_4 = createVector(rs, DataType.UNSIGNED_8, 4);
@@ -390,6 +425,153 @@
         return rs.mElement_UCHAR_4;
     }
 
+    public static Element I8_2(RenderScript rs) {
+        if(rs.mElement_CHAR_2 == null) {
+            rs.mElement_CHAR_2 = createVector(rs, DataType.SIGNED_8, 2);
+        }
+        return rs.mElement_CHAR_2;
+    }
+
+    public static Element I8_3(RenderScript rs) {
+        if(rs.mElement_CHAR_3 == null) {
+            rs.mElement_CHAR_3 = createVector(rs, DataType.SIGNED_8, 3);
+        }
+        return rs.mElement_CHAR_3;
+    }
+
+    public static Element I8_4(RenderScript rs) {
+        if(rs.mElement_CHAR_4 == null) {
+            rs.mElement_CHAR_4 = createVector(rs, DataType.SIGNED_8, 4);
+        }
+        return rs.mElement_CHAR_4;
+    }
+
+    public static Element U16_2(RenderScript rs) {
+        if(rs.mElement_USHORT_2 == null) {
+            rs.mElement_USHORT_2 = createVector(rs, DataType.UNSIGNED_16, 2);
+        }
+        return rs.mElement_USHORT_2;
+    }
+
+    public static Element U16_3(RenderScript rs) {
+        if(rs.mElement_USHORT_3 == null) {
+            rs.mElement_USHORT_3 = createVector(rs, DataType.UNSIGNED_16, 3);
+        }
+        return rs.mElement_USHORT_3;
+    }
+
+    public static Element U16_4(RenderScript rs) {
+        if(rs.mElement_USHORT_4 == null) {
+            rs.mElement_USHORT_4 = createVector(rs, DataType.UNSIGNED_16, 4);
+        }
+        return rs.mElement_USHORT_4;
+    }
+
+    public static Element I16_2(RenderScript rs) {
+        if(rs.mElement_SHORT_2 == null) {
+            rs.mElement_SHORT_2 = createVector(rs, DataType.SIGNED_16, 2);
+        }
+        return rs.mElement_SHORT_2;
+    }
+
+    public static Element I16_3(RenderScript rs) {
+        if(rs.mElement_SHORT_3 == null) {
+            rs.mElement_SHORT_3 = createVector(rs, DataType.SIGNED_16, 3);
+        }
+        return rs.mElement_SHORT_3;
+    }
+
+    public static Element I16_4(RenderScript rs) {
+        if(rs.mElement_SHORT_4 == null) {
+            rs.mElement_SHORT_4 = createVector(rs, DataType.SIGNED_16, 4);
+        }
+        return rs.mElement_SHORT_4;
+    }
+
+    public static Element U32_2(RenderScript rs) {
+        if(rs.mElement_UINT_2 == null) {
+            rs.mElement_UINT_2 = createVector(rs, DataType.UNSIGNED_32, 2);
+        }
+        return rs.mElement_UINT_2;
+    }
+
+    public static Element U32_3(RenderScript rs) {
+        if(rs.mElement_UINT_3 == null) {
+            rs.mElement_UINT_3 = createVector(rs, DataType.UNSIGNED_32, 3);
+        }
+        return rs.mElement_UINT_3;
+    }
+
+    public static Element U32_4(RenderScript rs) {
+        if(rs.mElement_UINT_4 == null) {
+            rs.mElement_UINT_4 = createVector(rs, DataType.UNSIGNED_32, 4);
+        }
+        return rs.mElement_UINT_4;
+    }
+
+    public static Element I32_2(RenderScript rs) {
+        if(rs.mElement_INT_2 == null) {
+            rs.mElement_INT_2 = createVector(rs, DataType.SIGNED_32, 2);
+        }
+        return rs.mElement_INT_2;
+    }
+
+    public static Element I32_3(RenderScript rs) {
+        if(rs.mElement_INT_3 == null) {
+            rs.mElement_INT_3 = createVector(rs, DataType.SIGNED_32, 3);
+        }
+        return rs.mElement_INT_3;
+    }
+
+    public static Element I32_4(RenderScript rs) {
+        if(rs.mElement_INT_4 == null) {
+            rs.mElement_INT_4 = createVector(rs, DataType.SIGNED_32, 4);
+        }
+        return rs.mElement_INT_4;
+    }
+
+    public static Element U64_2(RenderScript rs) {
+        if(rs.mElement_ULONG_2 == null) {
+            rs.mElement_ULONG_2 = createVector(rs, DataType.UNSIGNED_64, 2);
+        }
+        return rs.mElement_ULONG_2;
+    }
+
+    public static Element U64_3(RenderScript rs) {
+        if(rs.mElement_ULONG_3 == null) {
+            rs.mElement_ULONG_3 = createVector(rs, DataType.UNSIGNED_64, 3);
+        }
+        return rs.mElement_ULONG_3;
+    }
+
+    public static Element U64_4(RenderScript rs) {
+        if(rs.mElement_ULONG_4 == null) {
+            rs.mElement_ULONG_4 = createVector(rs, DataType.UNSIGNED_64, 4);
+        }
+        return rs.mElement_ULONG_4;
+    }
+
+    public static Element I64_2(RenderScript rs) {
+        if(rs.mElement_LONG_2 == null) {
+            rs.mElement_LONG_2 = createVector(rs, DataType.SIGNED_64, 2);
+        }
+        return rs.mElement_LONG_2;
+    }
+
+    public static Element I64_3(RenderScript rs) {
+        if(rs.mElement_LONG_3 == null) {
+            rs.mElement_LONG_3 = createVector(rs, DataType.SIGNED_64, 3);
+        }
+        return rs.mElement_LONG_3;
+    }
+
+    public static Element I64_4(RenderScript rs) {
+        if(rs.mElement_LONG_4 == null) {
+            rs.mElement_LONG_4 = createVector(rs, DataType.SIGNED_64, 4);
+        }
+        return rs.mElement_LONG_4;
+    }
+
     public static Element MATRIX_4X4(RenderScript rs) {
         if(rs.mElement_MATRIX_4X4 == null) {
             rs.mElement_MATRIX_4X4 = createUser(rs, DataType.MATRIX_4X4);
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 2110e37..9941827 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -613,8 +613,43 @@
     Element mElement_FLOAT_2;
     Element mElement_FLOAT_3;
     Element mElement_FLOAT_4;
+
+    Element mElement_DOUBLE_2;
+    Element mElement_DOUBLE_3;
+    Element mElement_DOUBLE_4;
+
+    Element mElement_UCHAR_2;
+    Element mElement_UCHAR_3;
     Element mElement_UCHAR_4;
 
+    Element mElement_CHAR_2;
+    Element mElement_CHAR_3;
+    Element mElement_CHAR_4;
+
+    Element mElement_USHORT_2;
+    Element mElement_USHORT_3;
+    Element mElement_USHORT_4;
+
+    Element mElement_SHORT_2;
+    Element mElement_SHORT_3;
+    Element mElement_SHORT_4;
+
+    Element mElement_UINT_2;
+    Element mElement_UINT_3;
+    Element mElement_UINT_4;
+
+    Element mElement_INT_2;
+    Element mElement_INT_3;
+    Element mElement_INT_4;
+
+    Element mElement_ULONG_2;
+    Element mElement_ULONG_3;
+    Element mElement_ULONG_4;
+
+    Element mElement_LONG_2;
+    Element mElement_LONG_3;
+    Element mElement_LONG_4;
+
     Element mElement_MATRIX_4X4;
     Element mElement_MATRIX_3X3;
     Element mElement_MATRIX_2X2;
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 596781e..e64d8ac 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -74,7 +74,7 @@
 
 struct CacheLogger {
     CacheLogger() {
-        LOGD("Creating OpenGL renderer caches");
+        INIT_LOGD("Creating OpenGL renderer caches");
     }
 }; // struct CacheLogger
 
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 146e789..77e63d7 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -326,12 +326,17 @@
             return false;
         }
 
+        SkAutoLockPixels alp(*bitmap);
+
         GLuint texture;
         GLuint previousFbo;
 
         GLenum format;
         GLenum type;
 
+        GLenum error = GL_NO_ERROR;
+        bool status = false;
+
         switch (bitmap->config()) {
             case SkBitmap::kA8_Config:
                 format = GL_ALPHA;
@@ -352,10 +357,18 @@
                 break;
         }
 
+        float alpha = layer->alpha;
+        SkXfermode::Mode mode = layer->mode;
+
+        layer->mode = SkXfermode::kSrc_Mode;
+        layer->alpha = 255;
+        layer->fbo = fbo;
+
         glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
 
         glGenTextures(1, &texture);
+        if ((error = glGetError()) != GL_NO_ERROR) goto error;
 
         glActiveTexture(GL_TEXTURE0);
         glBindTexture(GL_TEXTURE_2D, texture);
@@ -368,39 +381,48 @@
 
         glTexImage2D(GL_TEXTURE_2D, 0, format, bitmap->width(), bitmap->height(),
                 0, format, type, NULL);
+        if ((error = glGetError()) != GL_NO_ERROR) goto error;
+
         glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                 GL_TEXTURE_2D, texture, 0);
+        if ((error = glGetError()) != GL_NO_ERROR) goto error;
 
-        glBindTexture(GL_TEXTURE_2D, layer->texture);
+        {
+            LayerRenderer renderer(layer);
+            renderer.setViewport(bitmap->width(), bitmap->height());
+            renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f,
+                    bitmap->width(), bitmap->height(), !layer->blend);
+            if ((error = glGetError()) != GL_NO_ERROR) goto error;
 
-        float alpha = layer->alpha;
-        SkXfermode::Mode mode = layer->mode;
+            {
+                Rect bounds;
+                bounds.set(0.0f, 0.0f, bitmap->width(), bitmap->height());
+                renderer.drawTextureLayer(layer, bounds);
 
-        layer->mode = SkXfermode::kSrc_Mode;
-        layer->alpha = 255;
-        layer->fbo = fbo;
+                glReadPixels(0, 0, bitmap->width(), bitmap->height(), format,
+                        type, bitmap->getPixels());
 
-        LayerRenderer renderer(layer);
-        renderer.setViewport(bitmap->width(), bitmap->height());
-        renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f,
-                bitmap->width(), bitmap->height(), !layer->blend);
+                if ((error = glGetError()) != GL_NO_ERROR) goto error;
+            }
 
-        Rect bounds;
-        bounds.set(0.0f, 0.0f, bitmap->width(), bitmap->height());
-        renderer.drawTextureLayer(layer, bounds);
+            status = true;
+        }
 
-        SkAutoLockPixels alp(*bitmap);
-        glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, type, bitmap->getPixels());
+error:
+#if DEBUG_OPENGL
+        if (error != GL_NO_ERROR) {
+            LOGD("GL error while copying layer into bitmap = 0x%x", error);
+        }
+#endif
 
         glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-
         layer->mode = mode;
         layer->alpha = alpha;
         layer->fbo = 0;
         glDeleteTextures(1, &texture);
         caches.fboCache.put(fbo);
 
-        return true;
+        return status;
     }
     return false;
 }
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 5343a05..88774c6 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -151,7 +151,6 @@
     mSaveCount = 1;
 
     glViewport(0, 0, mWidth, mHeight);
-
     glDisable(GL_DITHER);
 
     glEnable(GL_SCISSOR_TEST);
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index c9e0f6f..4e271c7 100644
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -92,13 +92,18 @@
     };
     private static final String ID_WHERE = Files.FileColumns._ID + "=?";
     private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
-    private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
-    private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND "
+
+    private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
+    private static final String FORMAT_WHERE = Files.FileColumns.PARENT + "=?";
+    private static final String PARENT_WHERE = Files.FileColumns.FORMAT + "=?";
+    private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
                                             + Files.FileColumns.FORMAT + "=?";
-    private static final String PARENT_STORAGE_WHERE = PARENT_WHERE + " AND "
-                                            + Files.FileColumns.STORAGE_ID + "=?";
-    private static final String PARENT_STORAGE_FORMAT_WHERE = PARENT_STORAGE_WHERE + " AND "
-                                            + Files.FileColumns.FORMAT + "=?";
+    private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
+                                            + Files.FileColumns.PARENT + "=?";
+    private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
+                                            + Files.FileColumns.PARENT + "=?";
+    private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
+                                            + Files.FileColumns.PARENT + "=?";
 
     private final MediaScanner mMediaScanner;
 
@@ -249,26 +254,67 @@
     }
 
     private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
-        if (storageID != 0) {
-            if (format != 0) {
-                return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
-                        PARENT_STORAGE_FORMAT_WHERE,
-                        new String[] { Integer.toString(parent), Integer.toString(storageID),
-                                Integer.toString(format) }, null);
+        if (storageID == 0xFFFFFFFF) {
+            // query all stores
+            if (format == 0) {
+                // query all formats
+                if (parent == 0) {
+                    // query all objects
+                    return mMediaProvider.query(mObjectsUri, ID_PROJECTION, null, null, null);
+                }
+                if (parent == 0xFFFFFFFF) {
+                    // all objects in root of store
+                    parent = 0;
+                }
+                return mMediaProvider.query(mObjectsUri, ID_PROJECTION, PARENT_WHERE,
+                        new String[] { Integer.toString(parent) }, null);
             } else {
-                return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
-                        PARENT_STORAGE_WHERE, new String[]
-                                { Integer.toString(parent), Integer.toString(storageID) }, null);
+                // query specific format
+                if (parent == 0) {
+                    // query all objects
+                    return mMediaProvider.query(mObjectsUri, ID_PROJECTION, FORMAT_WHERE,
+                            new String[] { Integer.toString(format) }, null);
+                }
+                if (parent == 0xFFFFFFFF) {
+                    // all objects in root of store
+                    parent = 0;
+                }
+                return mMediaProvider.query(mObjectsUri, ID_PROJECTION, FORMAT_PARENT_WHERE,
+                        new String[] { Integer.toString(format), Integer.toString(parent) }, null);
             }
         } else {
-            if (format != 0) {
-                return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
-                            PARENT_FORMAT_WHERE,
-                            new String[] { Integer.toString(parent), Integer.toString(format) },
-                             null);
+            // query specific store
+            if (format == 0) {
+                // query all formats
+                if (parent == 0) {
+                    // query all objects
+                    return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_WHERE,
+                            new String[] { Integer.toString(storageID) }, null);
+                }
+                if (parent == 0xFFFFFFFF) {
+                    // all objects in root of store
+                    parent = 0;
+                }
+                return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_PARENT_WHERE,
+                        new String[] { Integer.toString(storageID), Integer.toString(parent) },
+                        null);
             } else {
-                return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
-                            PARENT_WHERE, new String[] { Integer.toString(parent) }, null);
+                // query specific format
+                if (parent == 0) {
+                    // query all objects
+                    return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_FORMAT_WHERE,
+                            new String[] {  Integer.toString(storageID), Integer.toString(format) },
+                            null);
+                }
+                if (parent == 0xFFFFFFFF) {
+                    // all objects in root of store
+                    parent = 0;
+                }
+                return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_FORMAT_PARENT_WHERE,
+                        new String[] { Integer.toString(storageID),
+                                       Integer.toString(format),
+                                       Integer.toString(parent) },
+                        null);
             }
         }
     }
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 3ea13a6..4ffb2c0 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -23,7 +23,6 @@
 // XXX needed for timing latency
 #include <utils/Timers.h>
 
-#include <sys/resource.h>
 #include <media/AudioTrack.h>
 #include <media/mediaplayer.h>
 
diff --git a/media/libmedia/ToneGenerator.cpp b/media/libmedia/ToneGenerator.cpp
index 9f1b3d6..7c2200e 100644
--- a/media/libmedia/ToneGenerator.cpp
+++ b/media/libmedia/ToneGenerator.cpp
@@ -21,7 +21,6 @@
 #include <stdio.h>
 #include <math.h>
 #include <utils/Log.h>
-#include <sys/resource.h>
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 #include <cutils/properties.h>
diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
index 06fb103..d574ea3 100644
--- a/media/libmediaplayerservice/MetadataRetrieverClient.cpp
+++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
@@ -21,7 +21,6 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <sys/resource.h>
 #include <dirent.h>
 #include <unistd.h>
 
diff --git a/media/libstagefright/AACWriter.cpp b/media/libstagefright/AACWriter.cpp
index 8413208..d133e91 100644
--- a/media/libstagefright/AACWriter.cpp
+++ b/media/libstagefright/AACWriter.cpp
@@ -27,7 +27,6 @@
 #include <media/stagefright/MetaData.h>
 #include <media/mediarecorder.h>
 #include <sys/prctl.h>
-#include <sys/resource.h>
 #include <fcntl.h>
 
 namespace android {
diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp
index b10d52c..6436071 100644
--- a/media/libstagefright/AMRWriter.cpp
+++ b/media/libstagefright/AMRWriter.cpp
@@ -23,7 +23,6 @@
 #include <media/stagefright/MetaData.h>
 #include <media/mediarecorder.h>
 #include <sys/prctl.h>
-#include <sys/resource.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 58f03a0..58f6699 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -22,7 +22,6 @@
 
 #include <pthread.h>
 #include <sys/prctl.h>
-#include <sys/resource.h>
 
 #include <media/stagefright/MPEG4Writer.h>
 #include <media/stagefright/MediaBuffer.h>
diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp
index a08eb7b..100d8a3 100644
--- a/media/libstagefright/TimedEventQueue.cpp
+++ b/media/libstagefright/TimedEventQueue.cpp
@@ -30,7 +30,6 @@
 
 #include <sys/prctl.h>
 #include <sys/time.h>
-#include <sys/resource.h>
 
 #include <media/stagefright/MediaDebug.h>
 
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index 14968e8..d23aa3a 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -21,7 +21,6 @@
 #include <dlfcn.h>
 
 #include <sys/prctl.h>
-#include <sys/resource.h>
 
 #include "../include/OMX.h"
 
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 4a8fd3e..9ec73c4 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -533,12 +533,10 @@
     MtpStorageID storageID = mRequest.getParameter(1);      // 0xFFFFFFFF for all storage
     MtpObjectFormat format = mRequest.getParameter(2);      // 0 for all formats
     MtpObjectHandle parent = mRequest.getParameter(3);      // 0xFFFFFFFF for objects with no parent
-                                                            // 0x00000000 for all objects?
+                                                            // 0x00000000 for all objects
 
     if (!hasStorage(storageID))
         return MTP_RESPONSE_INVALID_STORAGE_ID;
-    if (parent == 0xFFFFFFFF)
-        parent = 0;
 
     MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
     mData.putAUInt32(handles);
@@ -552,11 +550,9 @@
     MtpStorageID storageID = mRequest.getParameter(1);      // 0xFFFFFFFF for all storage
     MtpObjectFormat format = mRequest.getParameter(2);      // 0 for all formats
     MtpObjectHandle parent = mRequest.getParameter(3);      // 0xFFFFFFFF for objects with no parent
-                                                            // 0x00000000 for all objects?
+                                                            // 0x00000000 for all objects
     if (!hasStorage(storageID))
         return MTP_RESPONSE_INVALID_STORAGE_ID;
-    if (parent == 0xFFFFFFFF)
-        parent = 0;
 
     int count = mDatabase->getNumObjects(storageID, format, parent);
     if (count >= 0) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 55f5280..6d8eab6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -25,6 +25,10 @@
             android:exported="true"
             />
 
+        <activity android:name=".usb.UsbPreferenceActivity"
+             android:theme="@*android:style/Theme.Holo.Dialog.Alert"
+             android:excludeFromRecents="true">
+        </activity>
         <activity android:name=".usb.UsbStorageActivity"
                 android:excludeFromRecents="true">
         </activity>
diff --git a/packages/SystemUI/res/layout/usb_preference_buttons.xml b/packages/SystemUI/res/layout/usb_preference_buttons.xml
new file mode 100644
index 0000000..babe07e
--- /dev/null
+++ b/packages/SystemUI/res/layout/usb_preference_buttons.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Check box that is displayed in the activity resolver UI for the user
+     to make their selection the preferred activity. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingLeft="14dip"
+    android:paddingRight="15dip"
+    android:orientation="vertical">
+
+    <Button
+        android:id="@+id/mtp_ptp_button"
+        android:text="@string/use_ptp_button_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:focusable="true"
+        android:clickable="true" />
+
+    <Button
+        android:id="@+id/installer_cd_button"
+        android:text="@string/installer_cd_button_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:focusable="true"
+        android:clickable="true" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8945da5..86e0cd0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -156,4 +156,13 @@
 
     <!-- Compatibility mode help screen: body text. [CHAR LIMIT=150] -->
     <string name="compat_mode_help_body">When an app was designed for a smaller screen, a zoom control will appear by the clock.</string>
+
+    <!-- Title for the USB function chooser in UsbPreferenceActivity. [CHAR LIMIT=30] -->
+    <string name="usb_preference_title">USB file transfer options</string>
+    <!-- Label for the MTP USB function in UsbPreferenceActivity. [CHAR LIMIT=50] -->
+    <string name="use_mtp_button_title">Mount as a media player (MTP)</string>
+    <!-- Label for the PTP USB function in UsbPreferenceActivity. [CHAR LIMIT=50] -->
+    <string name="use_ptp_button_title">Mount as a camera (PTP)</string>
+    <!-- Label for the installer CD image option in UsbPreferenceActivity. [CHAR LIMIT=50] -->
+    <string name="installer_cd_button_title">Install Android File Transfer application for Mac</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java
new file mode 100644
index 0000000..3ed44e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java
@@ -0,0 +1,91 @@
+/*

+ * 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.systemui.usb;

+

+import android.app.Activity;

+import android.app.AlertDialog;

+import android.content.Context;

+import android.content.DialogInterface;

+import android.hardware.usb.UsbManager;

+import android.os.Bundle;

+import android.view.LayoutInflater;

+import android.view.View;

+import android.util.Log;

+import android.widget.Button;

+

+import java.io.File;

+

+import com.android.systemui.R;

+

+public class UsbPreferenceActivity extends Activity implements View.OnClickListener  {

+

+    private static final String TAG = "UsbPreferenceActivity";

+

+    private UsbManager mUsbManager;

+    private String mCurrentFunction;

+    private String[] mFunctions;

+    private String mInstallerImagePath;

+    private Button mMtpPtpButton;

+    private Button mInstallerCdButton;

+    private boolean mPtpActive;

+

+    @Override

+    public void onCreate(Bundle icicle) {

+        super.onCreate(icicle);

+

+        mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);

+

+        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);

+        dialogBuilder.setTitle(getString(R.string.usb_preference_title));

+

+        LayoutInflater inflater = (LayoutInflater)getSystemService(

+                Context.LAYOUT_INFLATER_SERVICE);

+        View buttonView = inflater.inflate(R.layout.usb_preference_buttons, null);

+        dialogBuilder.setView(buttonView);

+        mMtpPtpButton = (Button)buttonView.findViewById(R.id.mtp_ptp_button);

+        mInstallerCdButton = (Button)buttonView.findViewById(R.id.installer_cd_button);

+        mMtpPtpButton.setOnClickListener(this);

+        mInstallerCdButton.setOnClickListener(this);

+

+        mPtpActive = mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP);

+        if (mPtpActive) {

+            mMtpPtpButton.setText(R.string.use_mtp_button_title);

+        }

+

+        mInstallerImagePath = getString(com.android.internal.R.string.config_isoImagePath);

+        if (!(new File(mInstallerImagePath)).exists()) {

+            mInstallerCdButton.setVisibility(View.GONE);

+        }

+

+        dialogBuilder.show();

+    }

+

+    public void onClick(View v) {

+        if (v.equals(mMtpPtpButton)) {

+            if (mPtpActive) {

+                mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_MTP);

+            } else {

+                mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_PTP);

+            }

+        } else if (v.equals(mInstallerCdButton)) {

+            mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_MASS_STORAGE);

+            mUsbManager.setMassStorageBackingFile(mInstallerImagePath);

+        }

+

+        finish();

+    }

+}

diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 07855d9..aa3dfa6 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -19,7 +19,7 @@
 import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
-import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 
 import android.bluetooth.BluetoothTetheringDataTracker;
 import android.content.ContentResolver;
@@ -71,6 +71,7 @@
 import com.android.server.connectivity.Vpn;
 
 import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -78,8 +79,10 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.GregorianCalendar;
+import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -108,8 +111,12 @@
 
     private Vpn mVpn;
 
+    /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
+    private Object mRulesLock = new Object();
     /** Currently active network rules by UID. */
     private SparseIntArray mUidRules = new SparseIntArray();
+    /** Set of ifaces that are costly. */
+    private HashSet<String> mMeteredIfaces = Sets.newHashSet();
 
     /**
      * Sometimes we want to refer to the individual network state
@@ -570,31 +577,35 @@
     }
 
     /**
-     * Check if UID is blocked from using the given {@link NetworkInfo}.
+     * Check if UID should be blocked from using the network represented by the
+     * given {@link NetworkStateTracker}.
      */
-    private boolean isNetworkBlocked(NetworkInfo info, int uid) {
-        synchronized (mUidRules) {
-            // 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);
+    private boolean isNetworkBlocked(NetworkStateTracker tracker, int uid) {
+        final String iface = tracker.getLinkProperties().getInterfaceName();
 
-            if (networkIsPaid && (uidRules & RULE_REJECT_PAID) != 0) {
-                return true;
-            }
-
-            // no restrictive rules; network is visible
-            return false;
+        final boolean networkCostly;
+        final int uidRules;
+        synchronized (mRulesLock) {
+            networkCostly = mMeteredIfaces.contains(iface);
+            uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
         }
+
+        if (networkCostly && (uidRules & RULE_REJECT_METERED) != 0) {
+            return true;
+        }
+
+        // no restrictive rules; network is visible
+        return false;
     }
 
     /**
-     * Return a filtered version of the given {@link NetworkInfo}, potentially
-     * marked {@link DetailedState#BLOCKED} based on
-     * {@link #isNetworkBlocked(NetworkInfo, int)}.
+     * Return a filtered {@link NetworkInfo}, potentially marked
+     * {@link DetailedState#BLOCKED} based on
+     * {@link #isNetworkBlocked(NetworkStateTracker, int)}.
      */
-    private NetworkInfo filterNetworkInfo(NetworkInfo info, int uid) {
-        if (isNetworkBlocked(info, uid)) {
+    private NetworkInfo getFilteredNetworkInfo(NetworkStateTracker tracker, int uid) {
+        NetworkInfo info = tracker.getNetworkInfo();
+        if (isNetworkBlocked(tracker, uid)) {
             // network is blocked; clone and override state
             info = new NetworkInfo(info);
             info.setDetailedState(DetailedState.BLOCKED, null, null);
@@ -634,7 +645,7 @@
         if (isNetworkTypeValid(networkType)) {
             final NetworkStateTracker tracker = mNetTrackers[networkType];
             if (tracker != null) {
-                info = filterNetworkInfo(tracker.getNetworkInfo(), uid);
+                info = getFilteredNetworkInfo(tracker, uid);
             }
         }
         return info;
@@ -645,10 +656,10 @@
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
         final ArrayList<NetworkInfo> result = Lists.newArrayList();
-        synchronized (mUidRules) {
+        synchronized (mRulesLock) {
             for (NetworkStateTracker tracker : mNetTrackers) {
                 if (tracker != null) {
-                    result.add(filterNetworkInfo(tracker.getNetworkInfo(), uid));
+                    result.add(getFilteredNetworkInfo(tracker, uid));
                 }
             }
         }
@@ -685,10 +696,10 @@
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
         final ArrayList<NetworkState> result = Lists.newArrayList();
-        synchronized (mUidRules) {
+        synchronized (mRulesLock) {
             for (NetworkStateTracker tracker : mNetTrackers) {
                 if (tracker != null) {
-                    final NetworkInfo info = filterNetworkInfo(tracker.getNetworkInfo(), uid);
+                    final NetworkInfo info = getFilteredNetworkInfo(tracker, uid);
                     result.add(new NetworkState(
                             info, tracker.getLinkProperties(), tracker.getLinkCapabilities()));
                 }
@@ -1139,15 +1150,15 @@
 
     private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
         @Override
-        public void onRulesChanged(int uid, int uidRules) {
+        public void onUidRulesChanged(int uid, int uidRules) {
             // only someone like NPMS should only be calling us
             mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
             if (LOGD_RULES) {
-                Slog.d(TAG, "onRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
+                Slog.d(TAG, "onUidRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
             }
 
-            synchronized (mUidRules) {
+            synchronized (mRulesLock) {
                 // skip update when we've already applied rules
                 final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL);
                 if (oldRules == uidRules) return;
@@ -1158,6 +1169,24 @@
             // TODO: dispatch into NMS to push rules towards kernel module
             // TODO: notify UID when it has requested targeted updates
         }
+
+        @Override
+        public void onMeteredIfacesChanged(String[] meteredIfaces) {
+            // only someone like NPMS should only be calling us
+            mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+            if (LOGD_RULES) {
+                Slog.d(TAG,
+                        "onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")");
+            }
+
+            synchronized (mRulesLock) {
+                mMeteredIfaces.clear();
+                for (String iface : meteredIfaces) {
+                    mMeteredIfaces.add(iface);
+                }
+            }
+        }
     };
 
     /**
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index c86f962..d3244ec 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -33,6 +33,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.hardware.usb.UsbManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
@@ -152,7 +153,6 @@
          * 600 series - Unsolicited broadcasts.
          */
         public static final int VolumeStateChange              = 605;
-        public static final int ShareAvailabilityChange        = 620;
         public static final int VolumeDiskInserted             = 630;
         public static final int VolumeDiskRemoved              = 631;
         public static final int VolumeBadRemoval               = 632;
@@ -167,6 +167,7 @@
     private String                                mExternalStoragePath;
     private PackageManagerService                 mPms;
     private boolean                               mUmsEnabling;
+    private boolean                               mUmsAvailable = false;
     // Used as a lock for methods that register/unregister listeners.
     final private ArrayList<MountServiceBinderListener> mListeners =
             new ArrayList<MountServiceBinderListener>();
@@ -525,6 +526,10 @@
                         }
                     }
                 }.start();
+            } else if (action.equals(UsbManager.ACTION_USB_STATE)) {
+                boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) &&
+                        intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false));
+                notifyShareAvailabilityChange(available);
             }
         }
     };
@@ -654,12 +659,6 @@
                     updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED);
                 }
 
-                try {
-                    boolean avail = doGetShareMethodAvailable("ums");
-                    notifyShareAvailabilityChange("ums", avail);
-                } catch (Exception ex) {
-                    Slog.w(TAG, "Failed to get share availability");
-                }
                 /*
                  * Now that we've done our initialization, release
                  * the hounds!
@@ -694,13 +693,6 @@
             notifyVolumeStateChange(
                     cooked[2], cooked[3], Integer.parseInt(cooked[7]),
                             Integer.parseInt(cooked[10]));
-        } else if (code == VoldResponseCode.ShareAvailabilityChange) {
-            // FMT: NNN Share method <method> now <available|unavailable>
-            boolean avail = false;
-            if (cooked[5].equals("available")) {
-                avail = true;
-            }
-            notifyShareAvailabilityChange(cooked[3], avail);
         } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
                    (code == VoldResponseCode.VolumeDiskRemoved) ||
                    (code == VoldResponseCode.VolumeBadRemoval)) {
@@ -835,42 +827,6 @@
         }
     }
 
-    private boolean doGetShareMethodAvailable(String method) {
-        ArrayList<String> rsp;
-        try {
-            rsp = mConnector.doCommand("share status " + method);
-        } catch (NativeDaemonConnectorException ex) {
-            Slog.e(TAG, "Failed to determine whether share method " + method + " is available.");
-            return false;
-        }
-
-        for (String line : rsp) {
-            String[] tok = line.split(" ");
-            if (tok.length < 3) {
-                Slog.e(TAG, "Malformed response to share status " + method);
-                return false;
-            }
-
-            int code;
-            try {
-                code = Integer.parseInt(tok[0]);
-            } catch (NumberFormatException nfe) {
-                Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
-                return false;
-            }
-            if (code == VoldResponseCode.ShareStatusResult) {
-                if (tok[2].equals("available"))
-                    return true;
-                return false;
-            } else {
-                Slog.e(TAG, String.format("Unexpected response code %d", code));
-                return false;
-            }
-        }
-        Slog.e(TAG, "Got an empty response");
-        return false;
-    }
-
     private int doMountVolume(String path) {
         int rc = StorageResultCode.OperationSucceeded;
 
@@ -1018,13 +974,9 @@
         return false;
     }
 
-    private void notifyShareAvailabilityChange(String method, final boolean avail) {
-        if (!method.equals("ums")) {
-           Slog.w(TAG, "Ignoring unsupported share method {" + method + "}");
-           return;
-        }
-
+    private void notifyShareAvailabilityChange(final boolean avail) {
         synchronized (mListeners) {
+            mUmsAvailable = avail;
             for (int i = mListeners.size() -1; i >= 0; i--) {
                 MountServiceBinderListener bl = mListeners.get(i);
                 try {
@@ -1189,8 +1141,13 @@
         // XXX: This will go away soon in favor of IMountServiceObserver
         mPms = (PackageManagerService) ServiceManager.getService("package");
 
-        mContext.registerReceiver(mBroadcastReceiver,
-                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_BOOT_COMPLETED);
+        // don't bother monitoring USB if mass storage is not supported on our primary volume
+        if (mPrimaryVolume != null && mPrimaryVolume.allowMassStorage()) {
+            filter.addAction(UsbManager.ACTION_USB_STATE);
+        }
+        mContext.registerReceiver(mBroadcastReceiver, filter, null, null);
 
         mHandlerThread = new HandlerThread("MountService");
         mHandlerThread.start();
@@ -1323,7 +1280,9 @@
         if (getUmsEnabling()) {
             return true;
         }
-        return doGetShareMethodAvailable("ums");
+        synchronized (mListeners) {
+            return mUmsAvailable;
+        }
     }
 
     public void setUsbMassStorageEnabled(boolean enable) {
@@ -1419,7 +1378,7 @@
         return doFormatVolume(path);
     }
 
-    public int []getStorageUsers(String path) {
+    public int[] getStorageUsers(String path) {
         validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
         try {
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index bb0c671..d6704f4 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -16,6 +16,10 @@
 
 package com.android.server;
 
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.net.INetworkManagementEventObserver;
@@ -37,6 +41,7 @@
 import java.io.DataInputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.Inet4Address;
@@ -59,8 +64,9 @@
     private static final int ADD = 1;
     private static final int REMOVE = 2;
 
-    /** Base path to UID-granularity network statistics. */
-    private static final File PATH_PROC_UID_STAT = new File("/proc/uid_stat");
+    @Deprecated
+    private static final File STATS_UIDSTAT = new File("/proc/uid_stat");
+    private static final File STATS_NETFILTER = new File("/proc/net/xt_qtaguid/stats");
 
     class NetdResponseCode {
         public static final int InterfaceListResult       = 110;
@@ -899,7 +905,7 @@
         for (String iface : ifaces) {
             final long rx = getInterfaceCounter(iface, true);
             final long tx = getInterfaceCounter(iface, false);
-            stats.addEntry(iface, NetworkStats.UID_ALL, rx, tx);
+            stats.addEntry(iface, UID_ALL, TAG_NONE, rx, tx);
         }
 
         return stats;
@@ -910,16 +916,11 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
 
-        final String[] knownUids = PATH_PROC_UID_STAT.list();
-        final NetworkStats stats = new NetworkStats(
-                SystemClock.elapsedRealtime(), knownUids.length);
-
-        for (String uid : knownUids) {
-            final int uidInt = Integer.parseInt(uid);
-            collectNetworkStatsDetail(stats, uidInt);
+        if (STATS_NETFILTER.exists()) {
+            return getNetworkStatsDetailNetfilter(UID_ALL);
+        } else {
+            return getNetworkStatsDetailUidstat(UID_ALL);
         }
-
-        return stats;
     }
 
     @Override
@@ -929,19 +930,84 @@
                     android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
         }
 
-        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
-        collectNetworkStatsDetail(stats, uid);
+        if (STATS_NETFILTER.exists()) {
+            return getNetworkStatsDetailNetfilter(uid);
+        } else {
+            return getNetworkStatsDetailUidstat(uid);
+        }
+    }
+
+    /**
+     * Build {@link NetworkStats} with detailed UID statistics.
+     */
+    private NetworkStats getNetworkStatsDetailNetfilter(int limitUid) {
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
+
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new FileReader(STATS_NETFILTER));
+
+            // assumes format from kernel:
+            // idx iface acct_tag_hex uid_tag_int rx_bytes tx_bytes
+
+            // skip first line, which is legend
+            String line = reader.readLine();
+            while ((line = reader.readLine()) != null) {
+                final StringTokenizer t = new StringTokenizer(line);
+
+                final String idx = t.nextToken();
+                final String iface = t.nextToken();
+
+                try {
+                    // TODO: kernel currently emits tag in upper half of long;
+                    // eventually switch to directly using int.
+                    final int tag = (int) (Long.parseLong(t.nextToken().substring(2), 16) >> 32);
+                    final int uid = Integer.parseInt(t.nextToken());
+                    final long rx = Long.parseLong(t.nextToken());
+                    final long tx = Long.parseLong(t.nextToken());
+
+                    if (limitUid == UID_ALL || limitUid == uid) {
+                        stats.addEntry(iface, uid, tag, rx, tx);
+                    }
+                } catch (NumberFormatException e) {
+                    Slog.w(TAG, "problem parsing stats for idx " + idx + ": " + e);
+                }
+            }
+        } catch (IOException e) {
+            Slog.w(TAG, "problem parsing stats: " + e);
+        } finally {
+            IoUtils.closeQuietly(reader);
+        }
+
         return stats;
     }
 
-    private void collectNetworkStatsDetail(NetworkStats stats, int uid) {
-        // TODO: kernel module will provide interface-level stats in future
-        // TODO: migrate these stats to come across netd in bulk, instead of all
-        // these individual file reads.
-        final File uidPath = new File(PATH_PROC_UID_STAT, Integer.toString(uid));
-        final long rx = readSingleLongFromFile(new File(uidPath, "tcp_rcv"));
-        final long tx = readSingleLongFromFile(new File(uidPath, "tcp_snd"));
-        stats.addEntry(NetworkStats.IFACE_ALL, uid, rx, tx);
+    /**
+     * Build {@link NetworkStats} with detailed UID statistics.
+     *
+     * @deprecated since this uses older "uid_stat" data, and doesn't provide
+     *             tag-level granularity or additional variables.
+     */
+    @Deprecated
+    private NetworkStats getNetworkStatsDetailUidstat(int limitUid) {
+        final String[] knownUids;
+        if (limitUid == UID_ALL) {
+            knownUids = STATS_UIDSTAT.list();
+        } else {
+            knownUids = new String[] { String.valueOf(limitUid) };
+        }
+
+        final NetworkStats stats = new NetworkStats(
+                SystemClock.elapsedRealtime(), knownUids.length);
+        for (String uid : knownUids) {
+            final int uidInt = Integer.parseInt(uid);
+            final File uidPath = new File(STATS_UIDSTAT, uid);
+            final long rx = readSingleLongFromFile(new File(uidPath, "tcp_rcv"));
+            final long tx = readSingleLongFromFile(new File(uidPath, "tcp_snd"));
+            stats.addEntry(IFACE_ALL, uidInt, TAG_NONE, rx, tx);
+        }
+
+        return stats;
     }
 
     public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2769004..a23bacf 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -406,8 +406,8 @@
             }
 
             try {
-                Slog.i(TAG, "USB Observer");
-                // Listen for USB changes
+                Slog.i(TAG, "USB Service");
+                // Manage USB host and device support
                 usb = new UsbService(context);
                 ServiceManager.addService(Context.USB_SERVICE, usb);
             } catch (Throwable e) {
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
index 510ff62..7266d7d 100644
--- a/services/java/com/android/server/ThrottleService.java
+++ b/services/java/com/android/server/ThrottleService.java
@@ -533,7 +533,8 @@
             long incWrite = 0;
             try {
                 final NetworkStats stats = mNMService.getNetworkStatsSummary();
-                final int index = stats.findIndex(mIface, NetworkStats.UID_ALL);
+                final int index = stats.findIndex(
+                        mIface, NetworkStats.UID_ALL, NetworkStats.TAG_NONE);
 
                 if (index != -1) {
                     incRead = stats.rx[index] - mLastRead;
diff --git a/services/java/com/android/server/net/InterfaceIdentity.java b/services/java/com/android/server/net/InterfaceIdentity.java
deleted file mode 100644
index ff86581..0000000
--- a/services/java/com/android/server/net/InterfaceIdentity.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.net;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.net.ProtocolException;
-import java.util.HashSet;
-
-/**
- * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
- * active on that interface.
- *
- * @hide
- */
-public class InterfaceIdentity extends HashSet<NetworkIdentity> {
-    private static final int VERSION_CURRENT = 1;
-
-    public InterfaceIdentity() {
-    }
-
-    public InterfaceIdentity(DataInputStream in) throws IOException {
-        final int version = in.readInt();
-        switch (version) {
-            case VERSION_CURRENT: {
-                final int size = in.readInt();
-                for (int i = 0; i < size; i++) {
-                    add(new NetworkIdentity(in));
-                }
-                break;
-            }
-            default: {
-                throw new ProtocolException("unexpected version: " + version);
-            }
-        }
-    }
-
-    public void writeToStream(DataOutputStream out) throws IOException {
-        out.writeInt(VERSION_CURRENT);
-        out.writeInt(size());
-        for (NetworkIdentity ident : this) {
-            ident.writeToStream(out);
-        }
-    }
-
-    /**
-     * Test if any {@link NetworkIdentity} on this interface matches the given
-     * template and IMEI.
-     */
-    public boolean matchesTemplate(int networkTemplate, String subscriberId) {
-        for (NetworkIdentity ident : this) {
-            if (ident.matchesTemplate(networkTemplate, subscriberId)) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/services/java/com/android/server/net/NetworkIdentity.java b/services/java/com/android/server/net/NetworkIdentity.java
deleted file mode 100644
index 4a207f7..0000000
--- a/services/java/com/android/server/net/NetworkIdentity.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * 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.net;
-
-import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.ConnectivityManager.TYPE_WIMAX;
-import static android.net.ConnectivityManager.isNetworkTypeMobile;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_4G;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
-import static android.net.TrafficStats.TEMPLATE_WIFI;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_4_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_UNKNOWN;
-import static android.telephony.TelephonyManager.getNetworkClass;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.NetworkState;
-import android.telephony.TelephonyManager;
-
-import com.android.internal.util.Objects;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.net.ProtocolException;
-
-/**
- * Identity of a {@link NetworkInfo}, defined by network type and billing
- * relationship (such as IMSI).
- *
- * @hide
- */
-public class NetworkIdentity {
-    private static final int VERSION_CURRENT = 1;
-
-    public final int type;
-    public final int subType;
-    public final String subscriberId;
-
-    public NetworkIdentity(int type, int subType, String subscriberId) {
-        this.type = type;
-        this.subType = subType;
-        this.subscriberId = subscriberId;
-    }
-
-    public NetworkIdentity(DataInputStream in) throws IOException {
-        final int version = in.readInt();
-        switch (version) {
-            case VERSION_CURRENT: {
-                type = in.readInt();
-                subType = in.readInt();
-                subscriberId = readOptionalString(in);
-                break;
-            }
-            default: {
-                throw new ProtocolException("unexpected version: " + version);
-            }
-        }
-    }
-
-    public void writeToStream(DataOutputStream out) throws IOException {
-        out.writeInt(VERSION_CURRENT);
-        out.writeInt(type);
-        out.writeInt(subType);
-        writeOptionalString(out, subscriberId);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(type, subType, subscriberId);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj instanceof NetworkIdentity) {
-            final NetworkIdentity ident = (NetworkIdentity) obj;
-            return type == ident.type && subType == ident.subType
-                    && Objects.equal(subscriberId, ident.subscriberId);
-        }
-        return false;
-    }
-
-    @Override
-    public String toString() {
-        final String typeName = ConnectivityManager.getNetworkTypeName(type);
-        final String subTypeName;
-        if (ConnectivityManager.isNetworkTypeMobile(type)) {
-            subTypeName = TelephonyManager.getNetworkTypeName(subType);
-        } else {
-            subTypeName = Integer.toString(subType);
-        }
-
-        return "[type=" + typeName + ", subType=" + subTypeName + ", subId=" + subscriberId + "]";
-    }
-
-    /**
-     * Test if this network matches the given template and IMEI.
-     */
-    public boolean matchesTemplate(int networkTemplate, String subscriberId) {
-        switch (networkTemplate) {
-            case TEMPLATE_MOBILE_ALL:
-                return matchesMobile(subscriberId);
-            case TEMPLATE_MOBILE_3G_LOWER:
-                return matchesMobile3gLower(subscriberId);
-            case TEMPLATE_MOBILE_4G:
-                return matchesMobile4g(subscriberId);
-            case TEMPLATE_WIFI:
-                return matchesWifi();
-            default:
-                throw new IllegalArgumentException("unknown network template");
-        }
-    }
-
-    /**
-     * Check if mobile network with matching IMEI. Also matches
-     * {@link #TYPE_WIMAX}.
-     */
-    private boolean matchesMobile(String subscriberId) {
-        if (isNetworkTypeMobile(type) && Objects.equal(this.subscriberId, subscriberId)) {
-            return true;
-        } else if (type == TYPE_WIMAX) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Check if mobile network classified 3G or lower with matching IMEI.
-     */
-    private boolean matchesMobile3gLower(String subscriberId) {
-        if (isNetworkTypeMobile(type)
-                && Objects.equal(this.subscriberId, subscriberId)) {
-            switch (getNetworkClass(subType)) {
-                case NETWORK_CLASS_UNKNOWN:
-                case NETWORK_CLASS_2_G:
-                case NETWORK_CLASS_3_G:
-                    return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Check if mobile network classified 4G with matching IMEI. Also matches
-     * {@link #TYPE_WIMAX}.
-     */
-    private boolean matchesMobile4g(String subscriberId) {
-        if (isNetworkTypeMobile(type)
-                && Objects.equal(this.subscriberId, subscriberId)) {
-            switch (getNetworkClass(subType)) {
-                case NETWORK_CLASS_4_G:
-                    return true;
-            }
-        } else if (type == TYPE_WIMAX) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Check if matches Wi-Fi network template.
-     */
-    private boolean matchesWifi() {
-        if (type == TYPE_WIFI) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Build a {@link NetworkIdentity} from the given {@link NetworkState},
-     * assuming that any mobile networks are using the current IMSI.
-     */
-    public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state) {
-        final int type = state.networkInfo.getType();
-        final int subType = state.networkInfo.getSubtype();
-
-        // TODO: consider moving subscriberId over to LinkCapabilities, so it
-        // comes from an authoritative source.
-
-        final String subscriberId;
-        if (isNetworkTypeMobile(type)) {
-            final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
-                    Context.TELEPHONY_SERVICE);
-            subscriberId = telephony.getSubscriberId();
-        } else {
-            subscriberId = null;
-        }
-        return new NetworkIdentity(type, subType, subscriberId);
-    }
-
-    private static void writeOptionalString(DataOutputStream out, String value) throws IOException {
-        if (value != null) {
-            out.writeByte(1);
-            out.writeUTF(value);
-        } else {
-            out.writeByte(0);
-        }
-    }
-
-    private static String readOptionalString(DataInputStream in) throws IOException {
-        if (in.readByte() != 0) {
-            return in.readUTF();
-        } else {
-            return null;
-        }
-    }
-
-}
diff --git a/services/java/com/android/server/net/NetworkIdentitySet.java b/services/java/com/android/server/net/NetworkIdentitySet.java
new file mode 100644
index 0000000..af03fb3
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkIdentitySet.java
@@ -0,0 +1,98 @@
+/*
+ * 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.net;
+
+import android.net.NetworkIdentity;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.HashSet;
+
+/**
+ * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
+ * active on that interface.
+ *
+ * @hide
+ */
+public class NetworkIdentitySet extends HashSet<NetworkIdentity> {
+    private static final int VERSION_INIT = 1;
+    private static final int VERSION_ADD_ROAMING = 2;
+
+    public NetworkIdentitySet() {
+    }
+
+    public NetworkIdentitySet(DataInputStream in) throws IOException {
+        final int version = in.readInt();
+        switch (version) {
+            case VERSION_INIT: {
+                final int size = in.readInt();
+                for (int i = 0; i < size; i++) {
+                    final int ignoredVersion = in.readInt();
+                    final int type = in.readInt();
+                    final int subType = in.readInt();
+                    final String subscriberId = readOptionalString(in);
+                    add(new NetworkIdentity(type, subType, subscriberId, false));
+                }
+                break;
+            }
+            case VERSION_ADD_ROAMING: {
+                final int size = in.readInt();
+                for (int i = 0; i < size; i++) {
+                    final int type = in.readInt();
+                    final int subType = in.readInt();
+                    final String subscriberId = readOptionalString(in);
+                    final boolean roaming = in.readBoolean();
+                    add(new NetworkIdentity(type, subType, subscriberId, roaming));
+                }
+                break;
+            }
+            default: {
+                throw new ProtocolException("unexpected version: " + version);
+            }
+        }
+    }
+
+    public void writeToStream(DataOutputStream out) throws IOException {
+        out.writeInt(VERSION_ADD_ROAMING);
+        out.writeInt(size());
+        for (NetworkIdentity ident : this) {
+            out.writeInt(ident.getType());
+            out.writeInt(ident.getSubType());
+            writeOptionalString(out, ident.getSubscriberId());
+            out.writeBoolean(ident.getRoaming());
+        }
+    }
+
+    private static void writeOptionalString(DataOutputStream out, String value) throws IOException {
+        if (value != null) {
+            out.writeByte(1);
+            out.writeUTF(value);
+        } else {
+            out.writeByte(0);
+        }
+    }
+
+    private static String readOptionalString(DataInputStream in) throws IOException {
+        if (in.readByte() != 0) {
+            return in.readUTF();
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 9cbe82d..584cd03 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -22,24 +22,26 @@
 import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.EXTRA_UID;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT;
 import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_WARNING;
 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
-import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.dumpPolicy;
 import static android.net.NetworkPolicyManager.dumpRules;
 import static android.net.NetworkPolicyManager.isUidValidForPolicy;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_4G;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
-import static android.net.TrafficStats.isNetworkTemplateMobile;
+import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
+import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
@@ -61,9 +63,11 @@
 import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
+import android.net.NetworkIdentity;
 import android.net.NetworkPolicy;
 import android.net.NetworkState;
 import android.net.NetworkStats;
+import android.net.NetworkTemplate;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -84,9 +88,9 @@
 import com.android.internal.R;
 import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.Objects;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -103,6 +107,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 
 import libcore.io.IoUtils;
 
@@ -164,6 +169,9 @@
     /** Current derived network rules for each UID. */
     private SparseIntArray mUidRules = new SparseIntArray();
 
+    /** Set of ifaces that are metered. */
+    private HashSet<String> mMeteredIfaces = Sets.newHashSet();
+
     /** Foreground at both UID and PID granularity. */
     private SparseBooleanArray mUidForeground = new SparseBooleanArray();
     private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
@@ -241,9 +249,12 @@
         mContext.registerReceiver(mScreenReceiver, screenFilter);
 
         // watch for network interfaces to be claimed
-        final IntentFilter ifaceFilter = new IntentFilter();
-        ifaceFilter.addAction(CONNECTIVITY_ACTION);
-        mContext.registerReceiver(mIfaceReceiver, ifaceFilter, CONNECTIVITY_INTERNAL, mHandler);
+        final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
+        mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
+
+        // listen for uid removal to clean policy
+        final IntentFilter removedFilter = new IntentFilter(ACTION_UID_REMOVED);
+        mContext.registerReceiver(mRemovedReceiver, removedFilter, null, mHandler);
 
         // listen for warning polling events; currently dispatched by
         final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED);
@@ -306,6 +317,21 @@
         }
     };
 
+    private BroadcastReceiver mRemovedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and UID_REMOVED is protected
+            // broadcast.
+            final int uid = intent.getIntExtra(EXTRA_UID, 0);
+            synchronized (mRulesLock) {
+                // remove any policy and update rules to clean up
+                mUidPolicy.delete(uid);
+                updateRulesForUidLocked(uid);
+                writePolicyLocked();
+            }
+        }
+    };
+
     /**
      * Receiver that watches for {@link INetworkStatsService} updates, which we
      * use to check against {@link NetworkPolicy#warningBytes}.
@@ -348,10 +374,10 @@
             final long total;
             try {
                 final NetworkStats stats = mNetworkStats.getSummaryForNetwork(
-                        start, end, policy.networkTemplate, policy.subscriberId);
+                        policy.template, start, end);
                 total = stats.rx[0] + stats.tx[0];
             } catch (RemoteException e) {
-                Slog.w(TAG, "problem reading summary for template " + policy.networkTemplate);
+                Slog.w(TAG, "problem reading summary for template " + policy.template);
                 continue;
             }
 
@@ -375,8 +401,7 @@
      * notification of a specific type, like {@link #TYPE_LIMIT}.
      */
     private String buildNotificationTag(NetworkPolicy policy, int type) {
-        // TODO: consider splicing subscriberId hash into mix
-        return TAG + ":" + policy.networkTemplate + ":" + type;
+        return TAG + ":" + policy.template.hashCode() + ":" + type;
     }
 
     /**
@@ -403,7 +428,7 @@
 
                 final Intent intent = new Intent(ACTION_DATA_USAGE_WARNING);
                 intent.addCategory(Intent.CATEGORY_DEFAULT);
-                intent.putExtra(EXTRA_NETWORK_TEMPLATE, policy.networkTemplate);
+                intent.putExtra(EXTRA_NETWORK_TEMPLATE, policy.template.getMatchRule());
                 builder.setContentIntent(PendingIntent.getActivity(
                         mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
                 break;
@@ -411,11 +436,11 @@
             case TYPE_LIMIT: {
                 final String title;
                 final String body = res.getString(R.string.data_usage_limit_body);
-                switch (policy.networkTemplate) {
-                    case TEMPLATE_MOBILE_3G_LOWER:
+                switch (policy.template.getMatchRule()) {
+                    case MATCH_MOBILE_3G_LOWER:
                         title = res.getString(R.string.data_usage_3g_limit_title);
                         break;
-                    case TEMPLATE_MOBILE_4G:
+                    case MATCH_MOBILE_4G:
                         title = res.getString(R.string.data_usage_4g_limit_title);
                         break;
                     default:
@@ -430,7 +455,7 @@
 
                 final Intent intent = new Intent(ACTION_DATA_USAGE_LIMIT);
                 intent.addCategory(Intent.CATEGORY_DEFAULT);
-                intent.putExtra(EXTRA_NETWORK_TEMPLATE, policy.networkTemplate);
+                intent.putExtra(EXTRA_NETWORK_TEMPLATE, policy.template.getMatchRule());
                 builder.setContentIntent(PendingIntent.getActivity(
                         mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
                 break;
@@ -468,7 +493,7 @@
      * Receiver that watches for {@link IConnectivityManager} to claim network
      * interfaces. Used to apply {@link NetworkPolicy} to matching networks.
      */
-    private BroadcastReceiver mIfaceReceiver = new BroadcastReceiver() {
+    private BroadcastReceiver mConnReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and verified CONNECTIVITY_INTERNAL
@@ -516,7 +541,7 @@
             // collect all active ifaces that match this template
             ifaceList.clear();
             for (NetworkIdentity ident : networks.keySet()) {
-                if (ident.matchesTemplate(policy.networkTemplate, policy.subscriberId)) {
+                if (policy.template.matches(ident)) {
                     final String iface = networks.get(ident);
                     ifaceList.add(iface);
                 }
@@ -536,6 +561,8 @@
         final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
                 : System.currentTimeMillis();
 
+        mMeteredIfaces.clear();
+
         // apply each policy that we found ifaces for; compute remaining data
         // based on current cycle and historical stats, and push to kernel.
         for (NetworkPolicy policy : rules.keySet()) {
@@ -547,11 +574,10 @@
             final NetworkStats stats;
             final long total;
             try {
-                stats = mNetworkStats.getSummaryForNetwork(
-                        start, end, policy.networkTemplate, policy.subscriberId);
+                stats = mNetworkStats.getSummaryForNetwork(policy.template, start, end);
                 total = stats.rx[0] + stats.tx[0];
             } catch (RemoteException e) {
-                Slog.w(TAG, "problem reading summary for template " + policy.networkTemplate);
+                Slog.w(TAG, "problem reading summary for template " + policy.template);
                 continue;
             }
 
@@ -566,8 +592,27 @@
                 // remaining "quota" is based on usage in current cycle
                 final long quota = Math.max(0, policy.limitBytes - total);
                 //kernelSetIfacesQuota(ifaces, quota);
+
+                for (String iface : ifaces) {
+                    mMeteredIfaces.add(iface);
+                }
             }
         }
+
+        // dispatch changed rule to existing listeners
+        // TODO: dispatch outside of holding lock
+        final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]);
+        final int length = mListeners.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+            if (listener != null) {
+                try {
+                    listener.onMeteredIfacesChanged(meteredIfaces);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        mListeners.finishBroadcast();
     }
 
     /**
@@ -577,12 +622,13 @@
     private void ensureActiveMobilePolicyLocked() {
         if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyLocked()");
         final String subscriberId = getActiveSubscriberId();
+        final NetworkIdentity probeIdent = new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, false);
 
         // examine to see if any policy is defined for active mobile
         boolean mobileDefined = false;
         for (NetworkPolicy policy : mNetworkPolicy) {
-            if (isNetworkTemplateMobile(policy.networkTemplate)
-                    && Objects.equal(subscriberId, policy.subscriberId)) {
+            if (policy.template.matches(probeIdent)) {
                 mobileDefined = true;
             }
         }
@@ -598,8 +644,9 @@
             time.setToNow();
             final int cycleDay = time.monthDay;
 
-            mNetworkPolicy.add(new NetworkPolicy(
-                    TEMPLATE_MOBILE_ALL, subscriberId, cycleDay, 4 * GB_IN_BYTES, LIMIT_DISABLED));
+            final NetworkTemplate template = new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId);
+            mNetworkPolicy.add(
+                    new NetworkPolicy(template, cycleDay, 4 * GB_IN_BYTES, LIMIT_DISABLED));
             writePolicyLocked();
         }
     }
@@ -632,8 +679,10 @@
                         final long warningBytes = readLongAttribute(in, ATTR_WARNING_BYTES);
                         final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES);
 
-                        mNetworkPolicy.add(new NetworkPolicy(
-                                networkTemplate, subscriberId, cycleDay, warningBytes, limitBytes));
+                        final NetworkTemplate template = new NetworkTemplate(
+                                networkTemplate, subscriberId);
+                        mNetworkPolicy.add(
+                                new NetworkPolicy(template, cycleDay, warningBytes, limitBytes));
 
                     } else if (TAG_UID_POLICY.equals(tag)) {
                         final int uid = readIntAttribute(in, ATTR_UID);
@@ -675,10 +724,13 @@
 
             // write all known network policies
             for (NetworkPolicy policy : mNetworkPolicy) {
+                final NetworkTemplate template = policy.template;
+
                 out.startTag(null, TAG_NETWORK_POLICY);
-                writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, policy.networkTemplate);
-                if (policy.subscriberId != null) {
-                    out.attribute(null, ATTR_SUBSCRIBER_ID, policy.subscriberId);
+                writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule());
+                final String subscriberId = template.getSubscriberId();
+                if (subscriberId != null) {
+                    out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId);
                 }
                 writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay);
                 writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes);
@@ -754,17 +806,29 @@
 
         synchronized (mRulesLock) {
             // dispatch any existing rules to new listeners
+            // TODO: dispatch outside of holding lock
             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);
+                        listener.onUidRulesChanged(uid, uidRules);
                     } catch (RemoteException e) {
                     }
                 }
             }
+
+            // dispatch any metered ifaces to new listeners
+            // TODO: dispatch outside of holding lock
+            if (mMeteredIfaces.size() > 0) {
+                final String[] meteredIfaces = mMeteredIfaces.toArray(
+                        new String[mMeteredIfaces.size()]);
+                try {
+                    listener.onMeteredIfacesChanged(meteredIfaces);
+                } catch (RemoteException e) {
+                }
+            }
         }
     }
 
@@ -921,9 +985,9 @@
 
         // 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_METERED_BACKGROUND) != 0) {
+            // uid in background, and policy says to block metered data
+            uidRules = RULE_REJECT_METERED;
         }
 
         // TODO: only dispatch when rules actually change
@@ -931,16 +995,17 @@
         // record rule locally to dispatch to new listeners
         mUidRules.put(uid, uidRules);
 
-        final boolean rejectPaid = (uidRules & RULE_REJECT_PAID) != 0;
+        final boolean rejectMetered = (uidRules & RULE_REJECT_METERED) != 0;
         //kernelSetUidRejectPaid(uid, rejectPaid);
 
         // dispatch changed rule to existing listeners
+        // TODO: dispatch outside of holding lock
         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);
+                    listener.onUidRulesChanged(uid, uidRules);
                 } catch (RemoteException e) {
                 }
             }
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 0a84bc7..043a581 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -19,14 +19,19 @@
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
-import static android.Manifest.permission.SHUTDOWN;
+import static android.content.Intent.ACTION_SHUTDOWN;
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.EXTRA_UID;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
+import static android.net.TrafficStats.UID_REMOVED;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
 import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
 import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL;
+import static android.provider.Settings.Secure.NETSTATS_TAG_MAX_HISTORY;
 import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_UID_MAX_HISTORY;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -45,10 +50,12 @@
 import android.content.pm.ApplicationInfo;
 import android.net.IConnectivityManager;
 import android.net.INetworkStatsService;
+import android.net.NetworkIdentity;
 import android.net.NetworkInfo;
 import android.net.NetworkState;
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -57,14 +64,14 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.util.LongSparseArray;
 import android.util.NtpTrustedTime;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.TrustedTime;
 
 import com.android.internal.os.AtomicFile;
-import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -76,9 +83,9 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.ProtocolException;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 
 import libcore.io.IoUtils;
 
@@ -93,7 +100,10 @@
 
     /** File header magic number: "ANET" */
     private static final int FILE_MAGIC = 0x414E4554;
-    private static final int VERSION_CURRENT = 1;
+    private static final int VERSION_NETWORK_INIT = 1;
+    private static final int VERSION_UID_INIT = 1;
+    private static final int VERSION_UID_WITH_IDENT = 2;
+    private static final int VERSION_UID_WITH_TAG = 3;
 
     private final Context mContext;
     private final INetworkManagementService mNetworkManager;
@@ -112,6 +122,9 @@
     private PendingIntent mPollIntent;
 
     // TODO: listen for kernel push events through netd instead of polling
+    // TODO: watch for UID uninstall, and transfer stats into single bucket
+
+    // TODO: trim empty history objects entirely
 
     private static final long KB_IN_BYTES = 1024;
     private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES;
@@ -127,26 +140,27 @@
         public long getNetworkMaxHistory();
         public long getUidBucketDuration();
         public long getUidMaxHistory();
+        public long getTagMaxHistory();
         public long getTimeCacheMaxAge();
     }
 
     private final Object mStatsLock = new Object();
 
-    /** Set of active ifaces during this boot. */
-    private HashMap<String, InterfaceIdentity> mActiveIface = Maps.newHashMap();
-
-    /** Set of historical stats for known ifaces. */
-    private HashMap<InterfaceIdentity, NetworkStatsHistory> mNetworkStats = Maps.newHashMap();
+    /** Set of currently active ifaces. */
+    private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap();
+    /** Set of historical stats for known networks. */
+    private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkStats = Maps.newHashMap();
     /** Set of historical stats for known UIDs. */
-    private SparseArray<NetworkStatsHistory> mUidStats = new SparseArray<NetworkStatsHistory>();
+    private HashMap<NetworkIdentitySet, LongSparseArray<NetworkStatsHistory>> mUidStats =
+            Maps.newHashMap();
 
     /** Flag if {@link #mUidStats} have been loaded from disk. */
     private boolean mUidStatsLoaded = false;
 
-    private NetworkStats mLastNetworkPoll;
-    private NetworkStats mLastNetworkPersist;
+    private NetworkStats mLastNetworkSnapshot;
+    private NetworkStats mLastPersistNetworkSnapshot;
 
-    private NetworkStats mLastUidPoll;
+    private NetworkStats mLastUidSnapshot;
 
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
@@ -200,17 +214,20 @@
         }
 
         // watch for network interfaces to be claimed
-        final IntentFilter ifaceFilter = new IntentFilter();
-        ifaceFilter.addAction(CONNECTIVITY_ACTION);
-        mContext.registerReceiver(mIfaceReceiver, ifaceFilter, CONNECTIVITY_INTERNAL, mHandler);
+        final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
+        mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
 
         // listen for periodic polling events
         final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
         mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
 
+        // listen for uid removal to clean stats
+        final IntentFilter removedFilter = new IntentFilter(ACTION_UID_REMOVED);
+        mContext.registerReceiver(mRemovedReceiver, removedFilter, null, mHandler);
+
         // persist stats during clean shutdown
-        final IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
-        mContext.registerReceiver(mShutdownReceiver, shutdownFilter, SHUTDOWN, null);
+        final IntentFilter shutdownFilter = new IntentFilter(ACTION_SHUTDOWN);
+        mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
 
         try {
             registerPollAlarmLocked();
@@ -220,8 +237,9 @@
     }
 
     private void shutdownLocked() {
-        mContext.unregisterReceiver(mIfaceReceiver);
+        mContext.unregisterReceiver(mConnReceiver);
         mContext.unregisterReceiver(mPollReceiver);
+        mContext.unregisterReceiver(mRemovedReceiver);
         mContext.unregisterReceiver(mShutdownReceiver);
 
         writeNetworkStatsLocked();
@@ -251,18 +269,19 @@
     }
 
     @Override
-    public NetworkStatsHistory getHistoryForNetwork(int networkTemplate) {
+    public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
         synchronized (mStatsLock) {
             // combine all interfaces that match template
-            final String subscriberId = getActiveSubscriberId();
             final NetworkStatsHistory combined = new NetworkStatsHistory(
                     mSettings.getNetworkBucketDuration(), estimateNetworkBuckets());
-            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
-                final NetworkStatsHistory history = mNetworkStats.get(ident);
-                if (ident.matchesTemplate(networkTemplate, subscriberId)) {
-                    combined.recordEntireHistory(history);
+            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
+                if (templateMatches(template, ident)) {
+                    final NetworkStatsHistory history = mNetworkStats.get(ident);
+                    if (history != null) {
+                        combined.recordEntireHistory(history);
+                    }
                 }
             }
             return combined;
@@ -270,19 +289,30 @@
     }
 
     @Override
-    public NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate) {
+    public NetworkStatsHistory getHistoryForUid(NetworkTemplate template, int uid, int tag) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
         synchronized (mStatsLock) {
-            // TODO: combine based on template, if we store that granularity
             ensureUidStatsLoadedLocked();
-            return mUidStats.get(uid);
+            final long packed = packUidAndTag(uid, tag);
+
+            // combine all interfaces that match template
+            final NetworkStatsHistory combined = new NetworkStatsHistory(
+                    mSettings.getUidBucketDuration(), estimateUidBuckets());
+            for (NetworkIdentitySet ident : mUidStats.keySet()) {
+                if (templateMatches(template, ident)) {
+                    final NetworkStatsHistory history = mUidStats.get(ident).get(packed);
+                    if (history != null) {
+                        combined.recordEntireHistory(history);
+                    }
+                }
+            }
+            return combined;
         }
     }
 
     @Override
-    public NetworkStats getSummaryForNetwork(
-            long start, long end, int networkTemplate, String subscriberId) {
+    public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
         synchronized (mStatsLock) {
@@ -291,9 +321,9 @@
             long[] networkTotal = new long[2];
 
             // combine total from all interfaces that match template
-            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
-                final NetworkStatsHistory history = mNetworkStats.get(ident);
-                if (ident.matchesTemplate(networkTemplate, subscriberId)) {
+            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
+                if (templateMatches(template, ident)) {
+                    final NetworkStatsHistory history = mNetworkStats.get(ident);
                     networkTotal = history.getTotalData(start, end, networkTotal);
                     rx += networkTotal[0];
                     tx += networkTotal[1];
@@ -301,30 +331,45 @@
             }
 
             final NetworkStats stats = new NetworkStats(end - start, 1);
-            stats.addEntry(IFACE_ALL, UID_ALL, rx, tx);
+            stats.addEntry(IFACE_ALL, UID_ALL, TAG_NONE, rx, tx);
             return stats;
         }
     }
 
     @Override
-    public NetworkStats getSummaryForAllUid(long start, long end, int networkTemplate) {
+    public NetworkStats getSummaryForAllUid(
+            NetworkTemplate template, long start, long end, boolean includeTags) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
-        // TODO: apply networktemplate once granular uid stats are stored.
-
         synchronized (mStatsLock) {
             ensureUidStatsLoadedLocked();
 
-            final int size = mUidStats.size();
-            final NetworkStats stats = new NetworkStats(end - start, size);
-
+            final NetworkStats stats = new NetworkStats(end - start, 24);
             long[] total = new long[2];
-            for (int i = 0; i < size; i++) {
-                final int uid = mUidStats.keyAt(i);
-                final NetworkStatsHistory history = mUidStats.valueAt(i);
-                total = history.getTotalData(start, end, total);
-                stats.addEntry(IFACE_ALL, uid, total[0], total[1]);
+
+            for (NetworkIdentitySet ident : mUidStats.keySet()) {
+                if (templateMatches(template, ident)) {
+                    final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+                    for (int i = 0; i < uidStats.size(); i++) {
+                        final long packed = uidStats.keyAt(i);
+                        final int uid = unpackUid(packed);
+                        final int tag = unpackTag(packed);
+
+                        // always include summary under TAG_NONE, and include
+                        // other tags when requested.
+                        if (tag == TAG_NONE || includeTags) {
+                            final NetworkStatsHistory history = uidStats.valueAt(i);
+                            total = history.getTotalData(start, end, total);
+                            final long rx = total[0];
+                            final long tx = total[1];
+                            if (rx > 0 || tx > 0) {
+                                stats.combineEntry(IFACE_ALL, uid, tag, rx, tx);
+                            }
+                        }
+                    }
+                }
             }
+
             return stats;
         }
     }
@@ -334,7 +379,7 @@
      * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
      * with mobile interfaces.
      */
-    private BroadcastReceiver mIfaceReceiver = new BroadcastReceiver() {
+    private BroadcastReceiver mConnReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and verified CONNECTIVITY_INTERNAL
@@ -352,7 +397,19 @@
             // permission above.
             synchronized (mStatsLock) {
                 // TODO: acquire wakelock while performing poll
-                performPollLocked(true);
+                performPollLocked(true, false);
+            }
+        }
+    };
+
+    private BroadcastReceiver mRemovedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and UID_REMOVED is protected
+            // broadcast.
+            final int uid = intent.getIntExtra(EXTRA_UID, 0);
+            synchronized (mStatsLock) {
+                removeUidLocked(uid);
             }
         }
     };
@@ -360,7 +417,7 @@
     private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            // verified SHUTDOWN permission above.
+            // SHUTDOWN is protected broadcast.
             synchronized (mStatsLock) {
                 shutdownLocked();
             }
@@ -371,7 +428,7 @@
      * Inspect all current {@link NetworkState} to derive mapping from {@code
      * iface} to {@link NetworkStatsHistory}. When multiple {@link NetworkInfo}
      * are active on a single {@code iface}, they are combined under a single
-     * {@link InterfaceIdentity}.
+     * {@link NetworkIdentitySet}.
      */
     private void updateIfacesLocked() {
         if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
@@ -379,7 +436,7 @@
         // take one last stats snapshot before updating iface mapping. this
         // isn't perfect, since the kernel may already be counting traffic from
         // the updated network.
-        performPollLocked(false);
+        performPollLocked(false, false);
 
         final NetworkState[] states;
         try {
@@ -390,13 +447,19 @@
         }
 
         // rebuild active interfaces based on connected networks
-        mActiveIface.clear();
+        mActiveIfaces.clear();
 
         for (NetworkState state : states) {
             if (state.networkInfo.isConnected()) {
                 // collect networks under their parent interfaces
                 final String iface = state.linkProperties.getInterfaceName();
-                final InterfaceIdentity ident = findOrCreateInterfaceLocked(iface);
+
+                NetworkIdentitySet ident = mActiveIfaces.get(iface);
+                if (ident == null) {
+                    ident = new NetworkIdentitySet();
+                    mActiveIfaces.put(iface, ident);
+                }
+
                 ident.add(NetworkIdentity.buildNetworkIdentity(mContext, state));
             }
         }
@@ -409,7 +472,7 @@
      * @param detailedPoll Indicate if detailed UID stats should be collected
      *            during this poll operation.
      */
-    private void performPollLocked(boolean detailedPoll) {
+    private void performPollLocked(boolean detailedPoll, boolean forcePersist) {
         if (LOGV) Slog.v(TAG, "performPollLocked()");
 
         // try refreshing time source when stale
@@ -421,33 +484,34 @@
         final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
                 : System.currentTimeMillis();
 
-        final NetworkStats networkStats;
-        final NetworkStats uidStats;
+        final NetworkStats networkSnapshot;
+        final NetworkStats uidSnapshot;
         try {
-            networkStats = mNetworkManager.getNetworkStatsSummary();
-            uidStats = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null;
+            networkSnapshot = mNetworkManager.getNetworkStatsSummary();
+            uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null;
         } catch (RemoteException e) {
             Slog.w(TAG, "problem reading network stats");
             return;
         }
 
-        performNetworkPollLocked(networkStats, currentTime);
+        performNetworkPollLocked(networkSnapshot, currentTime);
         if (detailedPoll) {
-            performUidPollLocked(uidStats, currentTime);
+            performUidPollLocked(uidSnapshot, currentTime);
         }
 
         // decide if enough has changed to trigger persist
-        final NetworkStats persistDelta = computeStatsDelta(mLastNetworkPersist, networkStats);
+        final NetworkStats persistDelta = computeStatsDelta(
+                mLastPersistNetworkSnapshot, networkSnapshot);
         final long persistThreshold = mSettings.getPersistThreshold();
         for (String iface : persistDelta.getUniqueIfaces()) {
-            final int index = persistDelta.findIndex(iface, UID_ALL);
-            if (persistDelta.rx[index] > persistThreshold
+            final int index = persistDelta.findIndex(iface, UID_ALL, TAG_NONE);
+            if (forcePersist || persistDelta.rx[index] > persistThreshold
                     || persistDelta.tx[index] > persistThreshold) {
                 writeNetworkStatsLocked();
                 if (mUidStatsLoaded) {
                     writeUidStatsLocked();
                 }
-                mLastNetworkPersist = networkStats;
+                mLastPersistNetworkSnapshot = networkSnapshot;
                 break;
             }
         }
@@ -461,28 +525,33 @@
     /**
      * Update {@link #mNetworkStats} historical usage.
      */
-    private void performNetworkPollLocked(NetworkStats networkStats, long currentTime) {
-        final ArrayList<String> unknownIface = Lists.newArrayList();
+    private void performNetworkPollLocked(NetworkStats networkSnapshot, long currentTime) {
+        final HashSet<String> unknownIface = Sets.newHashSet();
 
-        final NetworkStats delta = computeStatsDelta(mLastNetworkPoll, networkStats);
+        final NetworkStats delta = computeStatsDelta(mLastNetworkSnapshot, networkSnapshot);
         final long timeStart = currentTime - delta.elapsedRealtime;
-        final long maxHistory = mSettings.getNetworkMaxHistory();
-        for (String iface : delta.getUniqueIfaces()) {
-            final InterfaceIdentity ident = mActiveIface.get(iface);
+        for (int i = 0; i < delta.size; i++) {
+            final String iface = delta.iface[i];
+            final NetworkIdentitySet ident = mActiveIfaces.get(iface);
             if (ident == null) {
                 unknownIface.add(iface);
                 continue;
             }
 
-            final int index = delta.findIndex(iface, UID_ALL);
-            final long rx = delta.rx[index];
-            final long tx = delta.tx[index];
+            final long rx = delta.rx[i];
+            final long tx = delta.tx[i];
 
-            final NetworkStatsHistory history = findOrCreateNetworkLocked(ident);
+            final NetworkStatsHistory history = findOrCreateNetworkStatsLocked(ident);
             history.recordData(timeStart, currentTime, rx, tx);
+        }
+
+        // trim any history beyond max
+        final long maxHistory = mSettings.getNetworkMaxHistory();
+        for (NetworkStatsHistory history : mNetworkStats.values()) {
             history.removeBucketsBefore(currentTime - maxHistory);
         }
-        mLastNetworkPoll = networkStats;
+
+        mLastNetworkSnapshot = networkSnapshot;
 
         if (LOGD && unknownIface.size() > 0) {
             Slog.w(TAG, "unknown interfaces " + unknownIface.toString() + ", ignoring those stats");
@@ -492,32 +561,84 @@
     /**
      * Update {@link #mUidStats} historical usage.
      */
-    private void performUidPollLocked(NetworkStats uidStats, long currentTime) {
+    private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) {
         ensureUidStatsLoadedLocked();
 
-        final NetworkStats delta = computeStatsDelta(mLastUidPoll, uidStats);
+        final NetworkStats delta = computeStatsDelta(mLastUidSnapshot, uidSnapshot);
         final long timeStart = currentTime - delta.elapsedRealtime;
-        final long maxHistory = mSettings.getUidMaxHistory();
-        for (int uid : delta.getUniqueUids()) {
-            // TODO: traverse all ifaces once surfaced in stats
-            final int index = delta.findIndex(IFACE_ALL, uid);
-            if (index != -1) {
-                final long rx = delta.rx[index];
-                final long tx = delta.tx[index];
 
-                final NetworkStatsHistory history = findOrCreateUidLocked(uid);
-                history.recordData(timeStart, currentTime, rx, tx);
-                history.removeBucketsBefore(currentTime - maxHistory);
+        for (int i = 0; i < delta.size; i++) {
+            final String iface = delta.iface[i];
+            final NetworkIdentitySet ident = mActiveIfaces.get(iface);
+            if (ident == null) {
+                continue;
+            }
+
+            final int uid = delta.uid[i];
+            final int tag = delta.tag[i];
+            final long rx = delta.rx[i];
+            final long tx = delta.tx[i];
+
+            final NetworkStatsHistory history = findOrCreateUidStatsLocked(ident, uid, tag);
+            history.recordData(timeStart, currentTime, rx, tx);
+        }
+
+        // trim any history beyond max
+        final long maxUidHistory = mSettings.getUidMaxHistory();
+        final long maxTagHistory = mSettings.getTagMaxHistory();
+        for (LongSparseArray<NetworkStatsHistory> uidStats : mUidStats.values()) {
+            for (int i = 0; i < uidStats.size(); i++) {
+                final long packed = uidStats.keyAt(i);
+                final NetworkStatsHistory history = uidStats.valueAt(i);
+
+                // detailed tags are trimmed sooner than summary in TAG_NONE
+                if (unpackTag(packed) == TAG_NONE) {
+                    history.removeBucketsBefore(currentTime - maxUidHistory);
+                } else {
+                    history.removeBucketsBefore(currentTime - maxTagHistory);
+                }
             }
         }
-        mLastUidPoll = uidStats;
+
+        mLastUidSnapshot = uidSnapshot;
     }
 
-    private NetworkStatsHistory findOrCreateNetworkLocked(InterfaceIdentity ident) {
-        final long bucketDuration = mSettings.getNetworkBucketDuration();
+    /**
+     * Clean up {@link #mUidStats} after UID is removed.
+     */
+    private void removeUidLocked(int uid) {
+        ensureUidStatsLoadedLocked();
+
+        // migrate all UID stats into special "removed" bucket
+        for (NetworkIdentitySet ident : mUidStats.keySet()) {
+            final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+            for (int i = 0; i < uidStats.size(); i++) {
+                final long packed = uidStats.keyAt(i);
+                if (unpackUid(packed) == uid) {
+                    // only migrate combined TAG_NONE history
+                    if (unpackTag(packed) == TAG_NONE) {
+                        final NetworkStatsHistory uidHistory = uidStats.valueAt(i);
+                        final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked(
+                                ident, UID_REMOVED, TAG_NONE);
+                        removedHistory.recordEntireHistory(uidHistory);
+                    }
+                    uidStats.remove(packed);
+                }
+            }
+        }
+
+        // TODO: push kernel event to wipe stats for UID, otherwise we risk
+        // picking them up again during next poll.
+
+        // since this was radical rewrite, push to disk
+        writeUidStatsLocked();
+    }
+
+    private NetworkStatsHistory findOrCreateNetworkStatsLocked(NetworkIdentitySet ident) {
         final NetworkStatsHistory existing = mNetworkStats.get(ident);
 
         // update when no existing, or when bucket duration changed
+        final long bucketDuration = mSettings.getNetworkBucketDuration();
         NetworkStatsHistory updated = null;
         if (existing == null) {
             updated = new NetworkStatsHistory(bucketDuration, 10);
@@ -535,11 +656,21 @@
         }
     }
 
-    private NetworkStatsHistory findOrCreateUidLocked(int uid) {
-        final long bucketDuration = mSettings.getUidBucketDuration();
-        final NetworkStatsHistory existing = mUidStats.get(uid);
+    private NetworkStatsHistory findOrCreateUidStatsLocked(
+            NetworkIdentitySet ident, int uid, int tag) {
+        ensureUidStatsLoadedLocked();
+
+        LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+        if (uidStats == null) {
+            uidStats = new LongSparseArray<NetworkStatsHistory>();
+            mUidStats.put(ident, uidStats);
+        }
+
+        final long packed = packUidAndTag(uid, tag);
+        final NetworkStatsHistory existing = uidStats.get(packed);
 
         // update when no existing, or when bucket duration changed
+        final long bucketDuration = mSettings.getUidBucketDuration();
         NetworkStatsHistory updated = null;
         if (existing == null) {
             updated = new NetworkStatsHistory(bucketDuration, 10);
@@ -550,22 +681,13 @@
         }
 
         if (updated != null) {
-            mUidStats.put(uid, updated);
+            uidStats.put(packed, updated);
             return updated;
         } else {
             return existing;
         }
     }
 
-    private InterfaceIdentity findOrCreateInterfaceLocked(String iface) {
-        InterfaceIdentity ident = mActiveIface.get(iface);
-        if (ident == null) {
-            ident = new InterfaceIdentity();
-            mActiveIface.put(iface, ident);
-        }
-        return ident;
-    }
-
     private void readNetworkStatsLocked() {
         if (LOGV) Slog.v(TAG, "readNetworkStatsLocked()");
 
@@ -585,15 +707,12 @@
 
             final int version = in.readInt();
             switch (version) {
-                case VERSION_CURRENT: {
-                    // file format is pairs of interfaces and stats:
-                    // network := size *(InterfaceIdentity NetworkStatsHistory)
-
+                case VERSION_NETWORK_INIT: {
+                    // network := size *(NetworkIdentitySet NetworkStatsHistory)
                     final int size = in.readInt();
                     for (int i = 0; i < size; i++) {
-                        final InterfaceIdentity ident = new InterfaceIdentity(in);
+                        final NetworkIdentitySet ident = new NetworkIdentitySet(in);
                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
-
                         mNetworkStats.put(ident, history);
                     }
                     break;
@@ -637,16 +756,39 @@
 
             final int version = in.readInt();
             switch (version) {
-                case VERSION_CURRENT: {
-                    // file format is pairs of UIDs and stats:
+                case VERSION_UID_INIT: {
                     // uid := size *(UID NetworkStatsHistory)
 
-                    final int size = in.readInt();
-                    for (int i = 0; i < size; i++) {
-                        final int uid = in.readInt();
-                        final NetworkStatsHistory history = new NetworkStatsHistory(in);
+                    // drop this data version, since we don't have a good
+                    // mapping into NetworkIdentitySet.
+                    break;
+                }
+                case VERSION_UID_WITH_IDENT: {
+                    // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
 
-                        mUidStats.put(uid, history);
+                    // drop this data version, since this version only existed
+                    // for a short time.
+                    break;
+                }
+                case VERSION_UID_WITH_TAG: {
+                    // uid := size *(NetworkIdentitySet size *(UID tag NetworkStatsHistory))
+                    final int ifaceSize = in.readInt();
+                    for (int i = 0; i < ifaceSize; i++) {
+                        final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+
+                        final int childSize = in.readInt();
+                        final LongSparseArray<NetworkStatsHistory> uidStats = new LongSparseArray<
+                                NetworkStatsHistory>(childSize);
+                        for (int j = 0; j < childSize; j++) {
+                            final int uid = in.readInt();
+                            final int tag = in.readInt();
+                            final long packed = packUidAndTag(uid, tag);
+
+                            final NetworkStatsHistory history = new NetworkStatsHistory(in);
+                            uidStats.put(packed, history);
+                        }
+
+                        mUidStats.put(ident, uidStats);
                     }
                     break;
                 }
@@ -674,10 +816,10 @@
             final DataOutputStream out = new DataOutputStream(fos);
 
             out.writeInt(FILE_MAGIC);
-            out.writeInt(VERSION_CURRENT);
+            out.writeInt(VERSION_NETWORK_INIT);
 
             out.writeInt(mNetworkStats.size());
-            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
+            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
                 final NetworkStatsHistory history = mNetworkStats.get(ident);
                 ident.writeToStream(out);
                 history.writeToStream(out);
@@ -694,6 +836,11 @@
     private void writeUidStatsLocked() {
         if (LOGV) Slog.v(TAG, "writeUidStatsLocked()");
 
+        if (!mUidStatsLoaded) {
+            Slog.w(TAG, "asked to write UID stats when not loaded; skipping");
+            return;
+        }
+
         // TODO: consider duplicating stats and releasing lock while writing
 
         FileOutputStream fos = null;
@@ -702,16 +849,25 @@
             final DataOutputStream out = new DataOutputStream(fos);
 
             out.writeInt(FILE_MAGIC);
-            out.writeInt(VERSION_CURRENT);
+            out.writeInt(VERSION_UID_WITH_TAG);
 
             final int size = mUidStats.size();
-
             out.writeInt(size);
-            for (int i = 0; i < size; i++) {
-                final int uid = mUidStats.keyAt(i);
-                final NetworkStatsHistory history = mUidStats.valueAt(i);
-                out.writeInt(uid);
-                history.writeToStream(out);
+            for (NetworkIdentitySet ident : mUidStats.keySet()) {
+                final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+                ident.writeToStream(out);
+
+                final int childSize = uidStats.size();
+                out.writeInt(childSize);
+                for (int i = 0; i < childSize; i++) {
+                    final long packed = uidStats.keyAt(i);
+                    final int uid = unpackUid(packed);
+                    final int tag = unpackTag(packed);
+                    final NetworkStatsHistory history = uidStats.valueAt(i);
+                    out.writeInt(uid);
+                    out.writeInt(tag);
+                    history.writeToStream(out);
+                }
             }
 
             mUidFile.finishWrite(fos);
@@ -740,35 +896,44 @@
             }
 
             if (argSet.contains("poll")) {
-                performPollLocked(true);
+                performPollLocked(true, true);
                 pw.println("Forced poll");
                 return;
             }
 
             pw.println("Active interfaces:");
-            for (String iface : mActiveIface.keySet()) {
-                final InterfaceIdentity ident = mActiveIface.get(iface);
+            for (String iface : mActiveIfaces.keySet()) {
+                final NetworkIdentitySet ident = mActiveIfaces.get(iface);
                 pw.print("  iface="); pw.print(iface);
                 pw.print(" ident="); pw.println(ident.toString());
             }
 
             pw.println("Known historical stats:");
-            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
-                final NetworkStatsHistory stats = mNetworkStats.get(ident);
+            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
+                final NetworkStatsHistory history = mNetworkStats.get(ident);
                 pw.print("  ident="); pw.println(ident.toString());
-                stats.dump("    ", pw);
+                history.dump("  ", pw);
             }
 
             if (argSet.contains("detail")) {
                 // since explicitly requested with argument, we're okay to load
                 // from disk if not already in memory.
                 ensureUidStatsLoadedLocked();
-                pw.println("Known UID stats:");
-                for (int i = 0; i < mUidStats.size(); i++) {
-                    final int uid = mUidStats.keyAt(i);
-                    final NetworkStatsHistory stats = mUidStats.valueAt(i);
-                    pw.print("  UID="); pw.println(uid);
-                    stats.dump("    ", pw);
+
+                pw.println("Detailed UID stats:");
+                for (NetworkIdentitySet ident : mUidStats.keySet()) {
+                    pw.print("  ident="); pw.println(ident.toString());
+
+                    final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+                    for (int i = 0; i < uidStats.size(); i++) {
+                        final long packed = uidStats.keyAt(i);
+                        final int uid = unpackUid(packed);
+                        final int tag = unpackTag(packed);
+                        final NetworkStatsHistory history = uidStats.valueAt(i);
+                        pw.print("    UID="); pw.print(uid);
+                        pw.print(" tag="); pw.println(tag);
+                        history.dump("    ", pw);
+                    }
                 }
             }
         }
@@ -779,27 +944,30 @@
      */
     @Deprecated
     private void generateRandomLocked() {
-        long end = System.currentTimeMillis();
-        long start = end - mSettings.getNetworkMaxHistory();
-        long rx = 3 * GB_IN_BYTES;
-        long tx = 2 * GB_IN_BYTES;
+        long networkEnd = System.currentTimeMillis();
+        long networkStart = networkEnd - mSettings.getNetworkMaxHistory();
+        long networkRx = 3 * GB_IN_BYTES;
+        long networkTx = 2 * GB_IN_BYTES;
+
+        long uidEnd = System.currentTimeMillis();
+        long uidStart = uidEnd - mSettings.getUidMaxHistory();
+        long uidRx = 500 * MB_IN_BYTES;
+        long uidTx = 100 * MB_IN_BYTES;
+
+        final List<ApplicationInfo> installedApps = mContext
+                .getPackageManager().getInstalledApplications(0);
 
         mNetworkStats.clear();
-        for (InterfaceIdentity ident : mActiveIface.values()) {
-            final NetworkStatsHistory stats = findOrCreateNetworkLocked(ident);
-            stats.generateRandom(start, end, rx, tx);
-        }
-
-        end = System.currentTimeMillis();
-        start = end - mSettings.getUidMaxHistory();
-        rx = 500 * MB_IN_BYTES;
-        tx = 100 * MB_IN_BYTES;
-
         mUidStats.clear();
-        for (ApplicationInfo info : mContext.getPackageManager().getInstalledApplications(0)) {
-            final int uid = info.uid;
-            final NetworkStatsHistory stats = findOrCreateUidLocked(uid);
-            stats.generateRandom(start, end, rx, tx);
+        for (NetworkIdentitySet ident : mActiveIfaces.values()) {
+            findOrCreateNetworkStatsLocked(ident).generateRandom(
+                    networkStart, networkEnd, networkRx, networkTx);
+
+            for (ApplicationInfo info : installedApps) {
+                final int uid = info.uid;
+                findOrCreateUidStatsLocked(ident, uid, TAG_NONE).generateRandom(
+                        uidStart, uidEnd, uidRx, uidTx);
+            }
         }
     }
 
@@ -815,12 +983,6 @@
         }
     }
 
-    private String getActiveSubscriberId() {
-        final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        return telephony.getSubscriberId();
-    }
-
     private int estimateNetworkBuckets() {
         return (int) (mSettings.getNetworkMaxHistory() / mSettings.getNetworkBucketDuration());
     }
@@ -833,6 +995,36 @@
         return (int) (existing.bucketCount * existing.bucketDuration / newBucketDuration);
     }
 
+    // @VisibleForTesting
+    public static long packUidAndTag(int uid, int tag) {
+        final long uidLong = uid;
+        final long tagLong = tag;
+        return (uidLong << 32) | (tagLong & 0xFFFFFFFFL);
+    }
+
+    // @VisibleForTesting
+    public static int unpackUid(long packed) {
+        return (int) (packed >> 32);
+    }
+
+    // @VisibleForTesting
+    public static int unpackTag(long packed) {
+        return (int) (packed & 0xFFFFFFFFL);
+    }
+
+    /**
+     * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
+     * in the given {@link NetworkIdentitySet}.
+     */
+    private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
+        for (NetworkIdentity ident : identSet) {
+            if (template.matches(ident)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Default external settings that read from {@link Settings.Secure}.
      */
@@ -866,6 +1058,9 @@
         public long getUidMaxHistory() {
             return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS);
         }
+        public long getTagMaxHistory() {
+            return getSecureLong(NETSTATS_TAG_MAX_HISTORY, 30 * DAY_IN_MILLIS);
+        }
         public long getTimeCacheMaxAge() {
             return DAY_IN_MILLIS;
         }
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index ca8a184..b7f9d5c 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -19,7 +19,6 @@
 import android.app.PendingIntent;
 import android.app.Notification;
 import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -33,10 +32,16 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.FileUtils;
 import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Parcelable;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
 import android.os.SystemProperties;
 import android.os.UEventObserver;
 import android.provider.Settings;
@@ -46,7 +51,7 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
-import java.io.FileReader;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -55,77 +60,110 @@
  * UsbDeviceManager manages USB state in device mode.
  */
 public class UsbDeviceManager {
+
     private static final String TAG = UsbDeviceManager.class.getSimpleName();
     private static final boolean LOG = false;
 
-    private static final String USB_CONNECTED_MATCH =
-            "DEVPATH=/devices/virtual/switch/usb_connected";
-    private static final String USB_CONFIGURATION_MATCH =
-            "DEVPATH=/devices/virtual/switch/usb_configuration";
-    private static final String USB_FUNCTIONS_MATCH =
-            "DEVPATH=/devices/virtual/usb_composite/";
-    private static final String USB_CONNECTED_PATH =
-            "/sys/class/switch/usb_connected/state";
-    private static final String USB_CONFIGURATION_PATH =
-            "/sys/class/switch/usb_configuration/state";
-    private static final String USB_COMPOSITE_CLASS_PATH =
-            "/sys/class/usb_composite";
+    private static final String USB_STATE_MATCH =
+            "DEVPATH=/devices/virtual/android_usb/android0";
+    private static final String ACCESSORY_START_MATCH =
+            "DEVPATH=/devices/virtual/misc/usb_accessory";
+    private static final String FUNCTIONS_PATH =
+            "/sys/class/android_usb/android0/functions";
+    private static final String STATE_PATH =
+            "/sys/class/android_usb/android0/state";
+    private static final String MASS_STORAGE_FILE_PATH =
+            "/sys/class/android_usb/f_mass_storage/lun/file";
 
     private static final int MSG_UPDATE_STATE = 0;
-    private static final int MSG_FUNCTION_ENABLED = 1;
-    private static final int MSG_FUNCTION_DISABLED = 2;
+    private static final int MSG_ENABLE_ADB = 1;
+    private static final int MSG_SET_PRIMARY_FUNCTION = 2;
+    private static final int MSG_SET_DEFAULT_FUNCTION = 3;
+    private static final int MSG_SYSTEM_READY = 4;
 
     // Delay for debouncing USB disconnects.
     // We often get rapid connect/disconnect events when enabling USB functions,
     // which need debouncing.
     private static final int UPDATE_DELAY = 1000;
 
-    // current connected and configuration state
-    private int mConnected;
-    private int mConfiguration;
-
-    // last broadcasted connected and configuration state
-    private int mLastConnected = -1;
-    private int mLastConfiguration = -1;
-
-    // lists of enabled and disabled USB functions
-    private final ArrayList<String> mEnabledFunctions = new ArrayList<String>();
-
+    private UsbHandler mHandler;
     private boolean mSystemReady;
 
-    private UsbAccessory mCurrentAccessory;
-    // USB functions that are enabled by default, to restore after exiting accessory mode
-    private final ArrayList<String> mDefaultFunctions = new ArrayList<String>();
-
     private final Context mContext;
-    ContentResolver mContentResolver;
-    private final Object mLock = new Object();
+    private final ContentResolver mContentResolver;
     private final UsbSettingsManager mSettingsManager;
     private NotificationManager mNotificationManager;
     private final boolean mHasUsbAccessory;
 
-    // for adb connected notifications
-    private boolean mAdbNotificationShown = false;
+    // for USB connected notification
+    private boolean mUsbNotificationShown;
+    private boolean mUseUsbNotification;
+    private Notification mUsbNotification;
+
+    // for adb connected notification
+    private boolean mAdbNotificationShown;
     private Notification mAdbNotification;
     private boolean mAdbEnabled;
 
+
     private class AdbSettingsObserver extends ContentObserver {
         public AdbSettingsObserver() {
             super(null);
         }
         @Override
         public void onChange(boolean selfChange) {
-            mAdbEnabled = (Settings.Secure.getInt(mContentResolver,
-                Settings.Secure.ADB_ENABLED, 0) > 0);
-            // setting this secure property will start or stop adbd
-           SystemProperties.set("persist.service.adb.enable", mAdbEnabled ? "1" : "0");
-           updateAdbNotification();
+            boolean enable = (Settings.Secure.getInt(mContentResolver,
+                    Settings.Secure.ADB_ENABLED, 0) > 0);
+            mHandler.sendMessage(MSG_ENABLE_ADB, enable);
         }
     }
 
-    private void updateAdbNotification() {
+    private void updateUsbNotification(boolean connected) {
+        if (mNotificationManager == null || !mUseUsbNotification) return;
+        if (connected) {
+            if (!mUsbNotificationShown) {
+                Resources r = mContext.getResources();
+                CharSequence title = r.getText(
+                        com.android.internal.R.string.usb_preferences_notification_title);
+                CharSequence message = r.getText(
+                        com.android.internal.R.string.usb_preferece_notification_message);
+
+                if (mUsbNotification == null) {
+                    mUsbNotification = new Notification();
+                    mUsbNotification.icon = com.android.internal.R.drawable.stat_sys_data_usb;
+                    mUsbNotification.when = 0;
+                    mUsbNotification.flags = Notification.FLAG_ONGOING_EVENT;
+                    mUsbNotification.tickerText = title;
+                    mUsbNotification.defaults = 0; // please be quiet
+                    mUsbNotification.sound = null;
+                    mUsbNotification.vibrate = null;
+                }
+
+                Intent intent = new Intent();
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                        Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                intent.setClassName("com.android.systemui",
+                        "com.android.systemui.usb.UsbPreferenceActivity");
+                PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+                        intent, 0);
+
+                mUsbNotification.setLatestEventInfo(mContext, title, message, pi);
+
+                mUsbNotificationShown = true;
+                mNotificationManager.notify(
+                        com.android.internal.R.string.usb_preferences_notification_title,
+                        mUsbNotification);
+            }
+
+        } else if (mUsbNotificationShown) {
+            mUsbNotificationShown = false;
+            mNotificationManager.cancel(
+                    com.android.internal.R.string.usb_preferences_notification_title);
+        }
+    }
+
+    private void updateAdbNotification(boolean adbEnabled) {
         if (mNotificationManager == null) return;
-        boolean adbEnabled = mAdbEnabled && (mConnected == 1);
         if (adbEnabled) {
             if ("0".equals(SystemProperties.get("persist.adb.notify"))) return;
 
@@ -173,38 +211,6 @@
         }
     }
 
-    private final void readCurrentAccessoryLocked() {
-        if (mHasUsbAccessory) {
-            String[] strings = nativeGetAccessoryStrings();
-            if (strings != null) {
-                mCurrentAccessory = new UsbAccessory(strings);
-                Log.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
-                if (mSystemReady) {
-                    mSettingsManager.accessoryAttached(mCurrentAccessory);
-                }
-            } else {
-                Log.e(TAG, "nativeGetAccessoryStrings failed");
-            }
-        }
-    }
-
-    /*
-     * Handles USB function enable/disable events
-     */
-    private final void functionEnabledLocked(String function, boolean enabled) {
-        if (enabled) {
-            if (!mEnabledFunctions.contains(function)) {
-                mEnabledFunctions.add(function);
-            }
-
-            if (UsbManager.USB_FUNCTION_ACCESSORY.equals(function)) {
-                readCurrentAccessoryLocked();
-            }
-        } else {
-            mEnabledFunctions.remove(function);
-        }
-    }
-
     /*
      * Listens for uevent messages from the kernel to monitor the USB state
      */
@@ -215,53 +221,13 @@
                 Slog.v(TAG, "USB UEVENT: " + event.toString());
             }
 
-            synchronized (mLock) {
-                String name = event.get("SWITCH_NAME");
-                String state = event.get("SWITCH_STATE");
-                if (name != null && state != null) {
-                    try {
-                        int intState = Integer.parseInt(state);
-                        if ("usb_connected".equals(name)) {
-                            mConnected = intState;
-                            // trigger an Intent broadcast
-                            if (mSystemReady) {
-                                // debounce disconnects to avoid problems bringing up USB tethering
-                                update(mConnected == 0);
-                            }
-                        } else if ("usb_configuration".equals(name)) {
-                            mConfiguration = intState;
-                            // trigger an Intent broadcast
-                            if (mSystemReady) {
-                                update(mConnected == 0);
-                            }
-                        }
-                    } catch (NumberFormatException e) {
-                        Slog.e(TAG, "Could not parse switch state from event " + event);
-                    }
-                } else {
-                    String function = event.get("FUNCTION");
-                    String enabledStr = event.get("ENABLED");
-                    if (function != null && enabledStr != null) {
-                        // Note: we do not broadcast a change when a function is enabled or disabled.
-                        // We just record the state change for the next broadcast.
-                        int what = ("1".equals(enabledStr) ?
-                                MSG_FUNCTION_ENABLED : MSG_FUNCTION_DISABLED);
-                        Message msg = Message.obtain(mHandler, what);
-                        msg.obj = function;
-                        mHandler.sendMessage(msg);
-                    }
-                }
-            }
-        }
-    };
-
-   private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
-        public void onReceive(Context context, Intent intent) {
-            // handle accessories attached at boot time
-            synchronized (mLock) {
-                if (mCurrentAccessory != null) {
-                    mSettingsManager.accessoryAttached(mCurrentAccessory);
-                }
+            String state = event.get("USB_STATE");
+            String accessory = event.get("ACCESSORY");
+            if (state != null) {
+                mHandler.updateState(state);
+            } else if ("START".equals(accessory)) {
+                Slog.d(TAG, "got accessory start");
+                setPrimaryFunction(UsbManager.USB_FUNCTION_ACCESSORY);
             }
         }
     };
@@ -273,229 +239,378 @@
         PackageManager pm = mContext.getPackageManager();
         mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
 
-        synchronized (mLock) {
-            init();  // set initial status
-
-            // make sure the ADB_ENABLED setting value matches the secure property value
-            mAdbEnabled = "1".equals(SystemProperties.get("persist.service.adb.enable"));
-            Settings.Secure.putInt(mContentResolver, Settings.Secure.ADB_ENABLED,
-                    mAdbEnabled ? 1 : 0);
-
-            // register observer to listen for settings changes
-            mContentResolver.registerContentObserver(
-                    Settings.Secure.getUriFor(Settings.Secure.ADB_ENABLED),
-                            false, new AdbSettingsObserver());
-
-            // Watch for USB configuration changes
-            if (mConfiguration >= 0) {
-                mUEventObserver.startObserving(USB_CONNECTED_MATCH);
-                mUEventObserver.startObserving(USB_CONFIGURATION_MATCH);
-                mUEventObserver.startObserving(USB_FUNCTIONS_MATCH);
+        // create a thread for our Handler
+        HandlerThread thread = new HandlerThread("UsbDeviceManager",
+                Process.THREAD_PRIORITY_BACKGROUND) {
+            protected void onLooperPrepared() {
+                mHandler = new UsbHandler();
             }
-        }
-    }
-
-    private final void init() {
-        char[] buffer = new char[1024];
-        boolean inAccessoryMode = false;
-
-        // Read initial USB state
-        mConfiguration = -1;
-        try {
-            FileReader file = new FileReader(USB_CONNECTED_PATH);
-            int len = file.read(buffer, 0, 1024);
-            file.close();
-            mConnected = Integer.valueOf((new String(buffer, 0, len)).trim());
-
-            file = new FileReader(USB_CONFIGURATION_PATH);
-            len = file.read(buffer, 0, 1024);
-            file.close();
-            mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim());
-
-        } catch (FileNotFoundException e) {
-            Slog.i(TAG, "This kernel does not have USB configuration switch support");
-        } catch (Exception e) {
-            Slog.e(TAG, "" , e);
-        }
-        if (mConfiguration < 0) {
-            // This may happen in the emulator or devices without USB device mode support
-            return;
-        }
-
-        // Read initial list of enabled and disabled functions (device mode)
-        try {
-            File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles();
-            for (int i = 0; i < files.length; i++) {
-                File file = new File(files[i], "enable");
-                FileReader reader = new FileReader(file);
-                int len = reader.read(buffer, 0, 1024);
-                reader.close();
-                int value = Integer.valueOf((new String(buffer, 0, len)).trim());
-                String functionName = files[i].getName();
-                if (value == 1) {
-                    mEnabledFunctions.add(functionName);
-                if (UsbManager.USB_FUNCTION_ACCESSORY.equals(functionName)) {
-                        // The USB accessory driver is on by default, but it might have been
-                        // enabled before the USB service has initialized.
-                        inAccessoryMode = true;
-                    } else if (!UsbManager.USB_FUNCTION_ADB.equals(functionName)) {
-                        // adb is enabled/disabled automatically by the adbd daemon,
-                        // so don't treat it as a default function.
-                        mDefaultFunctions.add(functionName);
-                    }
-                }
-            }
-        } catch (FileNotFoundException e) {
-            Slog.w(TAG, "This kernel does not have USB composite class support");
-        } catch (Exception e) {
-            Slog.e(TAG, "" , e);
-        }
-
-        // handle the case where an accessory switched the driver to accessory mode
-        // before the framework finished booting
-        if (inAccessoryMode) {
-            readCurrentAccessoryLocked();
-
-            // FIXME - if we booted in accessory mode, then we have no way to figure out
-            // which functions are enabled by default.
-            // For now, assume that MTP or mass storage are the only possibilities
-            if (!mEnabledFunctions.contains(UsbManager.USB_FUNCTION_MTP)) {
-                mDefaultFunctions.add(UsbManager.USB_FUNCTION_MTP);
-            } else if (!mEnabledFunctions.contains(UsbManager.USB_FUNCTION_MASS_STORAGE)) {
-                mDefaultFunctions.add(UsbManager.USB_FUNCTION_MASS_STORAGE);
-            }
-        }
+        };
+        thread.start();
     }
 
     public void systemReady() {
-        synchronized (mLock) {
-                mNotificationManager = (NotificationManager)
-                        mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        mSystemReady = true;
 
-            update(false);
-            if (mCurrentAccessory != null) {
-                Log.d(TAG, "accessoryAttached at systemReady");
-                // its still too early to handle accessories, so add a BOOT_COMPLETED receiver
-                // to handle this later.
-                mContext.registerReceiver(mBootCompletedReceiver,
-                        new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
-            }
-            mSystemReady = true;
+        mNotificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+
+        // We do not show the USB notification if the primary volume supports mass storage.
+        // The legacy mass storage UI will be used instead.
+        boolean massStorageSupported = false;
+        StorageManager storageManager = (StorageManager)
+                mContext.getSystemService(Context.STORAGE_SERVICE);
+        StorageVolume[] volumes = storageManager.getVolumeList();
+        if (volumes.length > 0) {
+            massStorageSupported = volumes[0].allowMassStorage();
         }
+        mUseUsbNotification = !massStorageSupported;
+
+        // make sure the ADB_ENABLED setting value matches the current state
+        Settings.Secure.putInt(mContentResolver, Settings.Secure.ADB_ENABLED, mAdbEnabled ? 1 : 0);
+
+        mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
     }
 
-    /*
-     * Sends a message to update the USB connected and configured state (device mode).
-     * If delayed is true, then we add a small delay in sending the message to debounce
-     * the USB connection when enabling USB tethering.
-     */
-    private final void update(boolean delayed) {
-        mHandler.removeMessages(MSG_UPDATE_STATE);
-        mHandler.sendEmptyMessageDelayed(MSG_UPDATE_STATE, delayed ? UPDATE_DELAY : 0);
-    }
-
-    /* returns the currently attached USB accessory (device mode) */
-    public UsbAccessory getCurrentAccessory() {
-        return mCurrentAccessory;
-    }
-
-    /* opens the currently attached USB accessory (device mode) */
-    public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
-        synchronized (mLock) {
-            if (mCurrentAccessory == null) {
-                throw new IllegalArgumentException("no accessory attached");
+     private static String addFunction(String functions, String function) {
+        if (!containsFunction(functions, function)) {
+            if (functions.length() > 0) {
+                functions += ",";
             }
-            if (!mCurrentAccessory.equals(accessory)) {
-                Log.e(TAG, accessory.toString() + " does not match current accessory "
-                        + mCurrentAccessory);
-                throw new IllegalArgumentException("accessory not attached");
-            }
-            mSettingsManager.checkPermission(mCurrentAccessory);
-            return nativeOpenAccessory();
+            functions += function;
         }
+        return functions;
     }
 
-    /*
-     * This handler is for deferred handling of events related to device mode and accessories.
-     */
-    private final Handler mHandler = new Handler() {
+    private static String removeFunction(String functions, String function) {
+        String[] split = functions.split(",");
+        for (int i = 0; i < split.length; i++) {
+            if (function.equals(split[i])) {
+                split[i] = null;
+            }
+        }
+        StringBuilder builder = new StringBuilder();
+         for (int i = 0; i < split.length; i++) {
+            String s = split[i];
+            if (s != null) {
+                if (builder.length() > 0) {
+                    builder.append(",");
+                }
+                builder.append(s);
+            }
+        }
+        return builder.toString();
+    }
 
-        @Override
-        public void handleMessage(Message msg) {
-            synchronized (mLock) {
-                switch (msg.what) {
-                    case MSG_UPDATE_STATE:
-                        if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) {
-                            updateAdbNotification();
-                            if (mConnected == 0) {
-                                if (UsbManager.isFunctionEnabled(
-                                            UsbManager.USB_FUNCTION_ACCESSORY)) {
-                                    // make sure accessory mode is off, and restore default functions
-                                    Log.d(TAG, "exited USB accessory mode");
-                                    if (!UsbManager.setFunctionEnabled
-                                            (UsbManager.USB_FUNCTION_ACCESSORY, false)) {
-                                        Log.e(TAG, "could not disable accessory function");
-                                    }
-                                    int count = mDefaultFunctions.size();
-                                    for (int i = 0; i < count; i++) {
-                                        String function = mDefaultFunctions.get(i);
-                                        if (!UsbManager.setFunctionEnabled(function, true)) {
-                                            Log.e(TAG, "could not reenable function " + function);
-                                        }
-                                    }
+    private static boolean containsFunction(String functions, String function) {
+        int index = functions.indexOf(function);
+        if (index < 0) return false;
+        if (index > 0 && functions.charAt(index - 1) != ',') return false;
+        int charAfter = index + function.length();
+        if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
+        return true;
+    }
 
-                                    if (mCurrentAccessory != null) {
-                                        mSettingsManager.accessoryDetached(mCurrentAccessory);
-                                        mCurrentAccessory = null;
-                                    }
-                                }
-                            }
+    private final class UsbHandler extends Handler {
 
-                            final ContentResolver cr = mContext.getContentResolver();
-                            if (Settings.Secure.getInt(cr,
-                                    Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
-                                Slog.i(TAG, "Device not provisioned, skipping USB broadcast");
-                                return;
-                            }
+        // current USB state
+        private boolean mConnected;
+        private boolean mConfigured;
+        private String mCurrentFunctions;
+        private String mDefaultFunctions;
+        private UsbAccessory mCurrentAccessory;
+        private boolean mDeferAccessoryAttached;
 
-                            mLastConnected = mConnected;
-                            mLastConfiguration = mConfiguration;
+        public UsbHandler() {
+            // Read initial USB state
+            try {
+                mCurrentFunctions = FileUtils.readTextFile(
+                        new File(FUNCTIONS_PATH), 0, null).trim();
+                mDefaultFunctions = mCurrentFunctions;
+                String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
+                updateState(state);
 
-                            // send a sticky broadcast containing current USB state
-                            Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
-                            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-                            intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0);
-                            intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration);
-                            for (int i = 0; i < mEnabledFunctions.size(); i++) {
-                                intent.putExtra(mEnabledFunctions.get(i), true);
-                            }
-                            mContext.sendStickyBroadcast(intent);
-                        }
-                        break;
-                    case MSG_FUNCTION_ENABLED:
-                    case MSG_FUNCTION_DISABLED:
-                        functionEnabledLocked((String)msg.obj, msg.what == MSG_FUNCTION_ENABLED);
-                        break;
+                mAdbEnabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB);
+
+                // Upgrade step for previous versions that used persist.service.adb.enable
+                String value = SystemProperties.get("persist.service.adb.enable", "");
+                if (value.length() > 0) {
+                    char enable = value.charAt(0);
+                    if (enable == '1') {
+                        setAdbEnabled(true);
+                    } else if (enable == '0') {
+                        setAdbEnabled(false);
+                    }
+                    SystemProperties.set("persist.service.adb.enable", "");
+                }
+
+                // register observer to listen for settings changes
+                mContentResolver.registerContentObserver(
+                        Settings.Secure.getUriFor(Settings.Secure.ADB_ENABLED),
+                                false, new AdbSettingsObserver());
+
+                // Watch for USB configuration changes
+                mUEventObserver.startObserving(USB_STATE_MATCH);
+                mUEventObserver.startObserving(ACCESSORY_START_MATCH);
+            } catch (Exception e) {
+                Slog.e(TAG, "Error initializing UsbHandler", e);
+            }
+        }
+
+        public void sendMessage(int what, boolean arg) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.arg1 = (arg ? 1 : 0);
+            sendMessage(m);
+        }
+
+        public void sendMessage(int what, Object arg) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.obj = arg;
+            sendMessage(m);
+        }
+
+        public void updateState(String state) {
+            int connected, configured;
+
+            if ("DISCONNECTED".equals(state)) {
+                connected = 0;
+                configured = 0;
+            } else if ("CONNECTED".equals(state)) {
+                connected = 1;
+                configured = 0;
+            } else if ("CONFIGURED".equals(state)) {
+                connected = 1;
+                configured = 1;
+            } else {
+                Slog.e(TAG, "unknown state " + state);
+                return;
+            }
+            removeMessages(MSG_UPDATE_STATE);
+            Message msg = Message.obtain(this, MSG_UPDATE_STATE);
+            msg.arg1 = connected;
+            msg.arg2 = configured;
+            // debounce disconnects to avoid problems bringing up USB tethering
+            sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0);
+        }
+
+        private boolean setUsbConfig(String config) {
+            // set the new configuration
+            SystemProperties.set("sys.usb.config", config);
+            // wait for the transition to complete.
+            // give up after 1 second.
+            for (int i = 0; i < 20; i++) {
+                // State transition is done when sys.usb.conf.done is set to the new configuration
+                if (config.equals(SystemProperties.get("sys.usb.state"))) return true;
+                try {
+                    // try again in 50ms
+                    Thread.sleep(50);
+                } catch (InterruptedException e) {
+                }
+            }
+            return false;
+        }
+
+        private void setCurrentFunctions(String functions) {
+            if (!mCurrentFunctions.equals(functions)) {
+                if (!setUsbConfig("none") || !setUsbConfig(functions)) {
+                    Log.e(TAG, "Failed to switch USB configuration to " + functions);
+                    // revert to previous configuration if we fail
+                    setUsbConfig(mCurrentFunctions);
+                } else {
+                    mCurrentFunctions = functions;
                 }
             }
         }
-    };
+
+        private void setAdbEnabled(boolean enable) {
+            if (enable != mAdbEnabled) {
+                mAdbEnabled = enable;
+                String functions;
+                if (enable) {
+                    functions = addFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB);
+                    mDefaultFunctions = addFunction(mDefaultFunctions,
+                            UsbManager.USB_FUNCTION_ADB);
+                } else {
+                    functions = removeFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB);
+                    mDefaultFunctions = removeFunction(mDefaultFunctions,
+                            UsbManager.USB_FUNCTION_ADB);
+                }
+                SystemProperties.set("persist.sys.usb.config", mDefaultFunctions);
+                setCurrentFunctions(functions);
+                updateAdbNotification(mAdbEnabled && mConnected);
+            }
+        }
+
+        private void setEnabledFunctions(String functionList) {
+            if (mAdbEnabled) {
+                functionList = addFunction(functionList, UsbManager.USB_FUNCTION_ADB);
+            } else {
+                functionList = removeFunction(functionList, UsbManager.USB_FUNCTION_ADB);
+            }
+            setCurrentFunctions(functionList);
+        }
+
+        private void updateCurrentAccessory() {
+            if (!mHasUsbAccessory) return;
+
+            if (mConfigured) {
+                String[] strings = nativeGetAccessoryStrings();
+                if (strings != null) {
+                    mCurrentAccessory = new UsbAccessory(strings);
+                    Log.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
+                    // defer accessoryAttached if system is not ready
+                    if (mSystemReady) {
+                        mSettingsManager.accessoryAttached(mCurrentAccessory);
+                    } else {
+                        mDeferAccessoryAttached = true;
+                    }
+                } else {
+                    Log.e(TAG, "nativeGetAccessoryStrings failed");
+                }
+            } else if (!mConnected) {
+                // make sure accessory mode is off
+                // and restore default functions
+                Log.d(TAG, "exited USB accessory mode");
+                setEnabledFunctions(mDefaultFunctions);
+
+                if (mCurrentAccessory != null) {
+                    if (mSystemReady) {
+                        mSettingsManager.accessoryDetached(mCurrentAccessory);
+                    }
+                    mCurrentAccessory = null;
+                }
+            }
+        }
+
+        private void updateUsbState() {
+            // send a sticky broadcast containing current USB state
+            Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
+            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+            intent.putExtra(UsbManager.USB_CONNECTED, mConnected);
+            intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured);
+
+            if (mCurrentFunctions != null) {
+                String[] functions = mCurrentFunctions.split(",");
+                for (int i = 0; i < functions.length; i++) {
+                    intent.putExtra(functions[i], true);
+                }
+            }
+
+            mContext.sendStickyBroadcast(intent);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            String function;
+
+            switch (msg.what) {
+                case MSG_UPDATE_STATE:
+                    mConnected = (msg.arg1 == 1);
+                    mConfigured = (msg.arg2 == 1);
+                    updateUsbNotification(mConnected);
+                    updateAdbNotification(mAdbEnabled && mConnected);
+                    if (containsFunction(mCurrentFunctions,
+                            UsbManager.USB_FUNCTION_ACCESSORY)) {
+                        updateCurrentAccessory();
+                    }
+
+                    if (!mConnected) {
+                        // restore defaults when USB is disconnected
+                        setCurrentFunctions(mDefaultFunctions);
+                    }
+                    if (mSystemReady) {
+                        updateUsbState();
+                    }
+                    break;
+                case MSG_ENABLE_ADB:
+                    setAdbEnabled(msg.arg1 == 1);
+                    break;
+                case MSG_SET_PRIMARY_FUNCTION:
+                    function = (String)msg.obj;
+                    if (function == null) {
+                        function = mDefaultFunctions;
+                    }
+                    setEnabledFunctions(function);
+                    break;
+                case MSG_SET_DEFAULT_FUNCTION:
+                    function = (String)msg.obj;
+                    if (mAdbEnabled) {
+                        function = addFunction(function, UsbManager.USB_FUNCTION_ADB);
+                    }
+                    SystemProperties.set("persist.sys.usb.config", function);
+                    mDefaultFunctions = function;
+                    break;
+                case MSG_SYSTEM_READY:
+                    updateUsbNotification(mConnected);
+                    updateAdbNotification(mAdbEnabled && mConnected);
+                    updateUsbState();
+                    if (mCurrentAccessory != null && mDeferAccessoryAttached) {
+                        mSettingsManager.accessoryAttached(mCurrentAccessory);
+                    }
+                    break;
+            }
+        }
+
+        public UsbAccessory getCurrentAccessory() {
+            return mCurrentAccessory;
+        }
+
+        public void dump(FileDescriptor fd, PrintWriter pw) {
+            pw.println("  USB Device State:");
+            pw.println("    Current Functions: " + mCurrentFunctions);
+            pw.println("    Default Functions: " + mDefaultFunctions);
+            pw.println("    mConnected: " + mConnected);
+            pw.println("    mConfigured: " + mConfigured);
+            pw.println("    mCurrentAccessory: " + mCurrentAccessory);
+        }
+    }
+
+    /* returns the currently attached USB accessory */
+    public UsbAccessory getCurrentAccessory() {
+        return mHandler.getCurrentAccessory();
+    }
+
+    /* opens the currently attached USB accessory */
+        public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
+            UsbAccessory currentAccessory = mHandler.getCurrentAccessory();
+            if (currentAccessory == null) {
+                throw new IllegalArgumentException("no accessory attached");
+            }
+            if (!currentAccessory.equals(accessory)) {
+                String error = accessory.toString()
+                        + " does not match current accessory "
+                        + currentAccessory;
+                throw new IllegalArgumentException(error);
+            }
+            mSettingsManager.checkPermission(accessory);
+            return nativeOpenAccessory();
+        }
+
+    public void setPrimaryFunction(String function) {
+        mHandler.sendMessage(MSG_SET_PRIMARY_FUNCTION, function);
+    }
+
+    public void setDefaultFunction(String function) {
+        if (function == null) {
+            throw new NullPointerException();
+        }
+        mHandler.sendMessage(MSG_SET_DEFAULT_FUNCTION, function);
+    }
+
+    public void setMassStorageBackingFile(String path) {
+        if (path == null) path = "";
+        try {
+            FileUtils.stringToFile(MASS_STORAGE_FILE_PATH, path);
+        } catch (IOException e) {
+           Slog.e(TAG, "failed to write to " + MASS_STORAGE_FILE_PATH);
+        }
+    }
 
     public void dump(FileDescriptor fd, PrintWriter pw) {
-        synchronized (mLock) {
-            pw.println("  USB Device State:");
-            pw.print("    Enabled Functions: ");
-            for (int i = 0; i < mEnabledFunctions.size(); i++) {
-                pw.print(mEnabledFunctions.get(i) + " ");
-            }
-            pw.println("");
-            pw.print("    Default Functions: ");
-            for (int i = 0; i < mDefaultFunctions.size(); i++) {
-                pw.print(mDefaultFunctions.get(i) + " ");
-            }
-            pw.println("");
-            pw.println("    mConnected: " + mConnected + ", mConfiguration: " + mConfiguration);
-            pw.println("    mCurrentAccessory: " + mCurrentAccessory);
+        if (mHandler != null) {
+            mHandler.dump(fd, pw);
         }
     }
 
diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java
index 21e5997c..193638f 100644
--- a/services/java/com/android/server/usb/UsbService.java
+++ b/services/java/com/android/server/usb/UsbService.java
@@ -50,7 +50,7 @@
         if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
             mHostManager = new UsbHostManager(context, mSettingsManager);
         }
-        if (new File("/sys/class/usb_composite").exists()) {
+        if (new File("/sys/class/android_usb").exists()) {
             mDeviceManager = new UsbDeviceManager(context, mSettingsManager);
         }
     }
@@ -92,7 +92,7 @@
     /* opens the currently attached USB accessory (device mode) */
     public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
         if (mDeviceManager != null) {
-            return openAccessory(accessory);
+            return mDeviceManager.openAccessory(accessory);
         } else {
             return null;
         }
@@ -146,6 +146,33 @@
         mSettingsManager.clearDefaults(packageName);
     }
 
+    public void setPrimaryFunction(String function) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        if (mDeviceManager != null) {
+            mDeviceManager.setPrimaryFunction(function);
+        } else {
+            throw new IllegalStateException("USB device mode not supported");
+        }
+    }
+
+    public void setDefaultFunction(String function) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        if (mDeviceManager != null) {
+            mDeviceManager.setDefaultFunction(function);
+        } else {
+            throw new IllegalStateException("USB device mode not supported");
+        }
+    }
+
+    public void setMassStorageBackingFile(String path) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        if (mDeviceManager != null) {
+            mDeviceManager.setMassStorageBackingFile(path);
+        } else {
+            throw new IllegalStateException("USB device mode not supported");
+        }
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index edccf6c..07e5425 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -16,16 +16,20 @@
 
 package com.android.server;
 
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.EXTRA_UID;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
-import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
+import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
-import static android.net.TrafficStats.TEMPLATE_WIFI;
+import static android.net.NetworkTemplate.MATCH_WIFI;
 import static org.easymock.EasyMock.anyInt;
+import static org.easymock.EasyMock.aryEq;
 import static org.easymock.EasyMock.capture;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.eq;
@@ -48,6 +52,7 @@
 import android.net.NetworkPolicy;
 import android.net.NetworkState;
 import android.net.NetworkStats;
+import android.net.NetworkTemplate;
 import android.os.Binder;
 import android.os.IPowerManager;
 import android.test.AndroidTestCase;
@@ -75,6 +80,8 @@
     private static final long TEST_START = 1194220800000L;
     private static final String TEST_IFACE = "test0";
 
+    private static NetworkTemplate sTemplateWifi = new NetworkTemplate(MATCH_WIFI, null);
+
     private BroadcastInterceptingContext mServiceContext;
     private File mPolicyDir;
 
@@ -182,7 +189,7 @@
         final Future<Intent> backgroundChanged = mServiceContext.nextBroadcastIntent(
                 ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
 
-        mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
 
         backgroundChanged.get();
     }
@@ -225,12 +232,12 @@
         expectRulesChanged(UID_A, RULE_ALLOW_ALL);
         replay();
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
-        mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
         verifyAndReset();
 
         // now turn screen off and verify REJECT rule
         expect(mPowerManager.isScreenOn()).andReturn(false).atLeastOnce();
-        expectRulesChanged(UID_A, RULE_REJECT_PAID);
+        expectRulesChanged(UID_A, RULE_REJECT_METERED);
         replay();
         mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
         verifyAndReset();
@@ -260,9 +267,9 @@
 
     public void testPolicyReject() throws Exception {
         // POLICY_REJECT should RULE_ALLOW in background
-        expectRulesChanged(UID_A, RULE_REJECT_PAID);
+        expectRulesChanged(UID_A, RULE_REJECT_METERED);
         replay();
-        mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
         verifyAndReset();
 
         // POLICY_REJECT should RULE_ALLOW in foreground
@@ -272,7 +279,7 @@
         verifyAndReset();
 
         // POLICY_REJECT should RULE_REJECT in background
-        expectRulesChanged(UID_A, RULE_REJECT_PAID);
+        expectRulesChanged(UID_A, RULE_REJECT_METERED);
         replay();
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
         verifyAndReset();
@@ -287,9 +294,9 @@
         verifyAndReset();
 
         // adding POLICY_REJECT should cause RULE_REJECT
-        expectRulesChanged(UID_A, RULE_REJECT_PAID);
+        expectRulesChanged(UID_A, RULE_REJECT_METERED);
         replay();
-        mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
         verifyAndReset();
 
         // removing POLICY_REJECT should return us to RULE_ALLOW
@@ -304,7 +311,7 @@
         final long currentTime = parseTime("2007-11-14T00:00:00.000Z");
         final long expectedCycle = parseTime("2007-11-05T00:00:00.000Z");
 
-        final NetworkPolicy policy = new NetworkPolicy(TEMPLATE_WIFI, null, 5, 1024L, 1024L);
+        final NetworkPolicy policy = new NetworkPolicy(sTemplateWifi, 5, 1024L, 1024L);
         final long actualCycle = computeLastCycleBoundary(currentTime, policy);
         assertEquals(expectedCycle, actualCycle);
     }
@@ -314,7 +321,7 @@
         final long currentTime = parseTime("2007-11-14T00:00:00.000Z");
         final long expectedCycle = parseTime("2007-10-20T00:00:00.000Z");
 
-        final NetworkPolicy policy = new NetworkPolicy(TEMPLATE_WIFI, null, 20, 1024L, 1024L);
+        final NetworkPolicy policy = new NetworkPolicy(sTemplateWifi, 20, 1024L, 1024L);
         final long actualCycle = computeLastCycleBoundary(currentTime, policy);
         assertEquals(expectedCycle, actualCycle);
     }
@@ -324,7 +331,7 @@
         final long currentTime = parseTime("2007-02-14T00:00:00.000Z");
         final long expectedCycle = parseTime("2007-01-30T00:00:00.000Z");
 
-        final NetworkPolicy policy = new NetworkPolicy(TEMPLATE_WIFI, null, 30, 1024L, 1024L);
+        final NetworkPolicy policy = new NetworkPolicy(sTemplateWifi, 30, 1024L, 1024L);
         final long actualCycle = computeLastCycleBoundary(currentTime, policy);
         assertEquals(expectedCycle, actualCycle);
     }
@@ -334,7 +341,7 @@
         final long currentTime = parseTime("2007-03-14T00:00:00.000Z");
         final long expectedCycle = parseTime("2007-03-01T00:00:00.000Z");
 
-        final NetworkPolicy policy = new NetworkPolicy(TEMPLATE_WIFI, null, 30, 1024L, 1024L);
+        final NetworkPolicy policy = new NetworkPolicy(sTemplateWifi, 30, 1024L, 1024L);
         final long actualCycle = computeLastCycleBoundary(currentTime, policy);
         assertEquals(expectedCycle, actualCycle);
     }
@@ -353,6 +360,7 @@
         state = new NetworkState[] { buildWifi() };
         expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
         expectTime(TIME_MAR_10 + elapsedRealtime);
+        expectMeteredIfacesChanged();
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
@@ -365,17 +373,34 @@
 
         // pretend that 512 bytes total have happened
         stats = new NetworkStats(elapsedRealtime, 1)
-                .addEntry(TEST_IFACE, UID_ALL, 256L, 256L);
-        expect(mStatsService.getSummaryForNetwork(TIME_FEB_15, TIME_MAR_10, TEMPLATE_WIFI, null))
+                .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 256L, 256L);
+        expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, TIME_MAR_10))
                 .andReturn(stats).atLeastOnce();
 
         // expect that quota remaining should be 1536 bytes
         // TODO: write up NetworkManagementService mock
 
         expectClearNotifications();
+        expectMeteredIfacesChanged(TEST_IFACE);
 
         replay();
-        setNetworkPolicies(new NetworkPolicy(TEMPLATE_WIFI, null, CYCLE_DAY, 1024L, 2048L));
+        setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1024L, 2048L));
+        verifyAndReset();
+    }
+
+    public void testUidRemovedPolicyCleared() throws Exception {
+        // POLICY_REJECT should RULE_REJECT in background
+        expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        replay();
+        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+        verifyAndReset();
+
+        // uninstall should clear RULE_REJECT
+        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        final Intent intent = new Intent(ACTION_UID_REMOVED);
+        intent.putExtra(EXTRA_UID, UID_A);
+        mServiceContext.sendBroadcast(intent);
         verifyAndReset();
     }
 
@@ -411,7 +436,12 @@
     }
 
     private void expectRulesChanged(int uid, int policy) throws Exception {
-        mPolicyListener.onRulesChanged(eq(uid), eq(policy));
+        mPolicyListener.onUidRulesChanged(eq(uid), eq(policy));
+        expectLastCall().atLeastOnce();
+    }
+
+    private void expectMeteredIfacesChanged(String... ifaces) throws Exception {
+        mPolicyListener.onMeteredIfacesChanged(aryEq(ifaces));
         expectLastCall().atLeastOnce();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 2457ff3..636d059 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -16,16 +16,26 @@
 
 package com.android.server;
 
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.EXTRA_UID;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
 import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
-import static android.net.TrafficStats.TEMPLATE_WIFI;
+import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.net.TrafficStats.UID_REMOVED;
 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.WEEK_IN_MILLIS;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static com.android.server.net.NetworkStatsService.packUidAndTag;
+import static com.android.server.net.NetworkStatsService.unpackTag;
+import static com.android.server.net.NetworkStatsService.unpackUid;
 import static org.easymock.EasyMock.anyLong;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.eq;
@@ -44,9 +54,12 @@
 import android.net.NetworkState;
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
 import android.os.INetworkManagementService;
+import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
 import android.util.TrustedTime;
 
 import com.android.server.net.NetworkStatsService;
@@ -66,8 +79,16 @@
     private static final String TEST_IFACE = "test0";
     private static final long TEST_START = 1194220800000L;
 
-    private static final int TEST_UID_1 = 1001;
-    private static final int TEST_UID_2 = 1002;
+    private static final String IMSI_1 = "310004";
+    private static final String IMSI_2 = "310260";
+
+    private static NetworkTemplate sTemplateWifi = new NetworkTemplate(MATCH_WIFI, null);
+    private static NetworkTemplate sTemplateImsi1 = new NetworkTemplate(MATCH_MOBILE_ALL, IMSI_1);
+    private static NetworkTemplate sTemplateImsi2 = new NetworkTemplate(MATCH_MOBILE_ALL, IMSI_2);
+
+    private static final int UID_RED = 1001;
+    private static final int UID_BLUE = 1002;
+    private static final int UID_GREEN = 1003;
 
     private BroadcastInterceptingContext mServiceContext;
     private File mStatsDir;
@@ -118,13 +139,15 @@
         mNetManager = null;
         mAlarmManager = null;
         mTime = null;
+        mSettings = null;
+        mConnManager = null;
 
         mService = null;
 
         super.tearDown();
     }
 
-    public void testSummaryStatsWifi() throws Exception {
+    public void testNetworkStatsWifi() throws Exception {
         long elapsedRealtime = 0;
 
         // pretend that wifi network comes online; service should ask about full
@@ -138,7 +161,7 @@
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
 
         // verify service has empty history for wifi
-        assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
+        assertNetworkTotal(sTemplateWifi, 0L, 0L);
         verifyAndReset();
 
         // modify some number on wifi, and trigger poll event
@@ -146,14 +169,14 @@
         expectTime(TEST_START + elapsedRealtime);
         expectDefaultSettings();
         expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
-                .addEntry(TEST_IFACE, UID_ALL, 1024L, 2048L));
+                .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 1024L, 2048L));
         expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
 
         // verify service recorded history
-        assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
+        assertNetworkTotal(sTemplateWifi, 1024L, 2048L);
         verifyAndReset();
 
         // and bump forward again, with counters going higher. this is
@@ -162,14 +185,14 @@
         expectTime(TEST_START + elapsedRealtime);
         expectDefaultSettings();
         expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
-                .addEntry(TEST_IFACE, UID_ALL, 4096L, 8192L));
+                .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 4096L, 8192L));
         expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
 
         // verify service recorded history
-        assertNetworkTotal(TEMPLATE_WIFI, 4096L, 8192L);
+        assertNetworkTotal(sTemplateWifi, 4096L, 8192L);
         verifyAndReset();
 
     }
@@ -189,7 +212,7 @@
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
 
         // verify service has empty history for wifi
-        assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
+        assertNetworkTotal(sTemplateWifi, 0L, 0L);
         verifyAndReset();
 
         // modify some number on wifi, and trigger poll event
@@ -197,19 +220,18 @@
         expectTime(TEST_START + elapsedRealtime);
         expectDefaultSettings();
         expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
-                .addEntry(TEST_IFACE, UID_ALL, 1024L, 2048L));
-        // TODO: switch these stats to specific iface
+                .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 1024L, 2048L));
         expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 2)
-                .addEntry(IFACE_ALL, TEST_UID_1, 512L, 256L)
-                .addEntry(IFACE_ALL, TEST_UID_2, 128L, 128L));
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 512L, 256L)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 128L, 128L));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
 
         // verify service recorded history
-        assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
-        assertUidTotal(TEST_UID_1, TEMPLATE_WIFI, 512L, 256L);
-        assertUidTotal(TEST_UID_2, TEMPLATE_WIFI, 128L, 128L);
+        assertNetworkTotal(sTemplateWifi, 1024L, 2048L);
+        assertUidTotal(sTemplateWifi, UID_RED, 512L, 256L);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 128L);
         verifyAndReset();
 
         // graceful shutdown system, which should trigger persist of stats, and
@@ -220,7 +242,7 @@
         // we persisted them to file.
         expectDefaultSettings();
         replay();
-        assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
+        assertNetworkTotal(sTemplateWifi, 0L, 0L);
         verifyAndReset();
 
         assertStatsFilesExist(true);
@@ -233,9 +255,9 @@
         mService.systemReady();
 
         // after systemReady(), we should have historical stats loaded again
-        assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
-        assertUidTotal(TEST_UID_1, TEMPLATE_WIFI, 512L, 256L);
-        assertUidTotal(TEST_UID_2, TEMPLATE_WIFI, 128L, 128L);
+        assertNetworkTotal(sTemplateWifi, 1024L, 2048L);
+        assertUidTotal(sTemplateWifi, UID_RED, 512L, 256L);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 128L);
         verifyAndReset();
 
     }
@@ -263,14 +285,14 @@
         expectTime(TEST_START + elapsedRealtime);
         expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
         expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
-                .addEntry(TEST_IFACE, UID_ALL, 512L, 512L));
+                .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 512L, 512L));
         expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
 
         // verify service recorded history
-        history = mService.getHistoryForNetwork(TEMPLATE_WIFI);
+        history = mService.getHistoryForNetwork(new NetworkTemplate(MATCH_WIFI, null));
         total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
         assertEquals(512L, total[0]);
         assertEquals(512L, total[1]);
@@ -289,7 +311,7 @@
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
 
         // verify identical stats, but spread across 4 buckets now
-        history = mService.getHistoryForNetwork(TEMPLATE_WIFI);
+        history = mService.getHistoryForNetwork(new NetworkTemplate(MATCH_WIFI, null));
         total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
         assertEquals(512L, total[0]);
         assertEquals(512L, total[1]);
@@ -299,15 +321,284 @@
 
     }
 
-    private void assertNetworkTotal(int template, long rx, long tx) {
+    public void testUidStatsAcrossNetworks() throws Exception {
+        long elapsedRealtime = 0;
+
+        // pretend first mobile network comes online
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildMobile3gState(IMSI_1));
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        verifyAndReset();
+
+        // create some traffic on first network
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 2048L, 512L));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 3)
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 1536L, 512L)
+                .addEntry(TEST_IFACE, UID_RED, 0xF00D, 512L, 512L)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 512L, 0L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify service recorded history
+        assertNetworkTotal(sTemplateImsi1, 2048L, 512L);
+        assertNetworkTotal(sTemplateWifi, 0L, 0L);
+        assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 512L);
+        assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 0L);
+        verifyAndReset();
+
+        // now switch networks; this also tests that we're okay with interfaces
+        // disappearing, to verify we don't count backwards.
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildMobile3gState(IMSI_2));
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        verifyAndReset();
+
+        // create traffic on second network
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 128L, 1024L));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 128L, 1024L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify original history still intact
+        assertNetworkTotal(sTemplateImsi1, 2048L, 512L);
+        assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 512L);
+        assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 0L);
+
+        // and verify new history also recorded under different template, which
+        // verifies that we didn't cross the streams.
+        assertNetworkTotal(sTemplateImsi2, 128L, 1024L);
+        assertNetworkTotal(sTemplateWifi, 0L, 0L);
+        assertUidTotal(sTemplateImsi2, UID_BLUE, 128L, 1024L);
+        verifyAndReset();
+
+    }
+
+    public void testUidRemovedIsMoved() throws Exception {
+        long elapsedRealtime = 0;
+
+        // pretend that network comes online
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildWifiState());
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        verifyAndReset();
+
+        // create some traffic
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 4128L, 544L));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 16L, 16L)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 4096L, 512L)
+                .addEntry(TEST_IFACE, UID_GREEN, TAG_NONE, 16L, 16L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify service recorded history
+        assertNetworkTotal(sTemplateWifi, 4128L, 544L);
+        assertUidTotal(sTemplateWifi, UID_RED, 16L, 16L);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 512L);
+        assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 16L);
+        verifyAndReset();
+
+        // now pretend two UIDs are uninstalled, which should migrate stats to
+        // special "removed" bucket.
+        expectDefaultSettings();
+        replay();
+        final Intent intent = new Intent(ACTION_UID_REMOVED);
+        intent.putExtra(EXTRA_UID, UID_BLUE);
+        mServiceContext.sendBroadcast(intent);
+        intent.putExtra(EXTRA_UID, UID_RED);
+        mServiceContext.sendBroadcast(intent);
+
+        // existing uid and total should remain unchanged; but removed UID
+        // should be gone completely.
+        assertNetworkTotal(sTemplateWifi, 4128L, 544L);
+        assertUidTotal(sTemplateWifi, UID_RED, 0L, 0L);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 0L, 0L);
+        assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 16L);
+        assertUidTotal(sTemplateWifi, UID_REMOVED, 4112L, 528L);
+        verifyAndReset();
+
+    }
+
+    public void testUid3g4gCombinedByTemplate() throws Exception {
+        long elapsedRealtime = 0;
+
+        // pretend that network comes online
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildMobile3gState(IMSI_1));
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        verifyAndReset();
+
+        // create some traffic
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 1024L, 1024L)
+                .addEntry(TEST_IFACE, UID_RED, 0xF00D, 512L, 512L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify service recorded history
+        assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 1024L);
+        verifyAndReset();
+
+        // now switch over to 4g network
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildMobile4gState());
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        verifyAndReset();
+
+        // create traffic on second network
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 512L, 256L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify that ALL_MOBILE template combines both
+        assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 1280L);
+
+        verifyAndReset();
+
+    }
+    
+    public void testPackedUidAndTag() throws Exception {
+        assertEquals(0x0000000000000000L, packUidAndTag(0, 0x0));
+        assertEquals(0x000003E900000000L, packUidAndTag(1001, 0x0));
+        assertEquals(0x000003E90000F00DL, packUidAndTag(1001, 0xF00D));
+
+        long packed;
+        packed = packUidAndTag(Integer.MAX_VALUE, Integer.MIN_VALUE);
+        assertEquals(Integer.MAX_VALUE, unpackUid(packed));
+        assertEquals(Integer.MIN_VALUE, unpackTag(packed));
+
+        packed = packUidAndTag(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        assertEquals(Integer.MIN_VALUE, unpackUid(packed));
+        assertEquals(Integer.MAX_VALUE, unpackTag(packed));
+
+        packed = packUidAndTag(10005, 0xFFFFFFFF);
+        assertEquals(10005, unpackUid(packed));
+        assertEquals(0xFFFFFFFF, unpackTag(packed));
+        
+    }
+
+    public void testSummaryForAllUid() throws Exception {
+        long elapsedRealtime = 0;
+
+        // pretend that network comes online
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildWifiState());
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        verifyAndReset();
+
+        // create some traffic for two apps
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 50L, 50L)
+                .addEntry(TEST_IFACE, UID_RED, 0xF00D, 10L, 10L)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 1024L, 512L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify service recorded history
+        assertUidTotal(sTemplateWifi, UID_RED, 50L, 50L);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 1024L, 512L);
+        verifyAndReset();
+        
+        // now create more traffic in next hour, but only for one app
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 2048L, 1024L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // first verify entire history present
+        NetworkStats stats = mService.getSummaryForAllUid(
+                sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+        assertEquals(3, stats.size);
+        assertStatsEntry(stats, 0, IFACE_ALL, UID_RED, TAG_NONE, 50L, 50L);
+        assertStatsEntry(stats, 1, IFACE_ALL, UID_RED, 0xF00D, 10L, 10L);
+        assertStatsEntry(stats, 2, IFACE_ALL, UID_BLUE, TAG_NONE, 2048L, 1024L);
+
+        // now verify that recent history only contains one uid
+        final long currentTime = TEST_START + elapsedRealtime;
+        stats = mService.getSummaryForAllUid(
+                sTemplateWifi, currentTime - HOUR_IN_MILLIS, currentTime, true);
+        assertEquals(1, stats.size);
+        assertStatsEntry(stats, 0, IFACE_ALL, UID_BLUE, TAG_NONE, 1024L, 512L);
+
+        verifyAndReset();
+    }
+
+    private void assertNetworkTotal(NetworkTemplate template, long rx, long tx) {
         final NetworkStatsHistory history = mService.getHistoryForNetwork(template);
         final long[] total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
         assertEquals(rx, total[0]);
         assertEquals(tx, total[1]);
     }
 
-    private void assertUidTotal(int uid, int template, long rx, long tx) {
-        final NetworkStatsHistory history = mService.getHistoryForUid(uid, template);
+    private void assertUidTotal(NetworkTemplate template, int uid, long rx, long tx) {
+        final NetworkStatsHistory history = mService.getHistoryForUid(template, uid, TAG_NONE);
         final long[] total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
         assertEquals(rx, total[0]);
         assertEquals(tx, total[1]);
@@ -346,6 +637,7 @@
         expect(mSettings.getNetworkMaxHistory()).andReturn(maxHistory).anyTimes();
         expect(mSettings.getUidBucketDuration()).andReturn(bucketDuration).anyTimes();
         expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes();
+        expect(mSettings.getTagMaxHistory()).andReturn(maxHistory).anyTimes();
         expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
     }
 
@@ -358,17 +650,26 @@
     }
 
     private void assertStatsFilesExist(boolean exist) {
-        final File summaryFile = new File(mStatsDir, "netstats.bin");
-        final File detailFile = new File(mStatsDir, "netstats_uid.bin");
+        final File networkFile = new File(mStatsDir, "netstats.bin");
+        final File uidFile = new File(mStatsDir, "netstats_uid.bin");
         if (exist) {
-            assertTrue(summaryFile.exists());
-            assertTrue(detailFile.exists());
+            assertTrue(networkFile.exists());
+            assertTrue(uidFile.exists());
         } else {
-            assertFalse(summaryFile.exists());
-            assertFalse(detailFile.exists());
+            assertFalse(networkFile.exists());
+            assertFalse(uidFile.exists());
         }
     }
 
+    private static void assertStatsEntry(
+            NetworkStats stats, int i, String iface, int uid, int tag, long rx, long tx) {
+        assertEquals(iface, stats.iface[i]);
+        assertEquals(uid, stats.uid[i]);
+        assertEquals(tag, stats.tag[i]);
+        assertEquals(rx, stats.rx[i]);
+        assertEquals(tx, stats.tx[i]);
+    }
+
     private static NetworkState buildWifiState() {
         final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
         info.setDetailedState(DetailedState.CONNECTED, null, null);
@@ -377,6 +678,23 @@
         return new NetworkState(info, prop, null);
     }
 
+    private static NetworkState buildMobile3gState(String subscriberId) {
+        final NetworkInfo info = new NetworkInfo(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UMTS, null, null);
+        info.setDetailedState(DetailedState.CONNECTED, null, null);
+        final LinkProperties prop = new LinkProperties();
+        prop.setInterfaceName(TEST_IFACE);
+        return new NetworkState(info, prop, null, subscriberId);
+    }
+
+    private static NetworkState buildMobile4gState() {
+        final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null);
+        info.setDetailedState(DetailedState.CONNECTED, null, null);
+        final LinkProperties prop = new LinkProperties();
+        prop.setInterfaceName(TEST_IFACE);
+        return new NetworkState(info, prop, null);
+    }
+
     private static NetworkStats buildEmptyStats(long elapsedRealtime) {
         return new NetworkStats(elapsedRealtime, 0);
     }
diff --git a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
index 30afdd8..2f275c3 100644
--- a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
@@ -289,7 +289,7 @@
     public void expectGetInterfaceCounter(long rx, long tx) throws Exception {
         // TODO: provide elapsedRealtime mock to match TimeAuthority
         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
-        stats.addEntry(TEST_IFACE, NetworkStats.UID_ALL, rx, tx);
+        stats.addEntry(TEST_IFACE, NetworkStats.UID_ALL, NetworkStats.TAG_NONE, rx, tx);
 
         expect(mMockNMService.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
     }
diff --git a/tests/BiDiTests/res/layout/view_padding_mixed.xml b/tests/BiDiTests/res/layout/view_padding_mixed.xml
new file mode 100644
index 0000000..092f55b
--- /dev/null
+++ b/tests/BiDiTests/res/layout/view_padding_mixed.xml
@@ -0,0 +1,135 @@
+<?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/view_padding_mixed"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+    <FrameLayout android:layout_width="match_parent"
+                 android:layout_height="match_parent">
+
+        <FrameLayout
+                android:layout_width="300dp"
+                android:layout_height="300dp"
+                android:layout_gravity="top|left"
+                android:background="#FF888888"
+                android:paddingTop="20dip"
+                android:paddingLeft="40dip"
+                android:paddingStart="5dip"
+                android:paddingBottom="30dip"
+                android:paddingRight="50dip"
+                android:layoutDirection="ltr">
+
+            <FrameLayout
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_gravity="top|left"
+                    android:background="#FF0000FF">
+            </FrameLayout>
+
+            <FrameLayout
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_gravity="bottom|right"
+                    android:background="#FF00FF00">
+            </FrameLayout>
+        </FrameLayout>
+
+        <FrameLayout
+                android:layout_width="300dp"
+                android:layout_height="300dp"
+                android:layout_gravity="top|center_horizontal"
+                android:background="#FF888888"
+                android:paddingTop="20dip"
+                android:paddingLeft="40dip"
+                android:paddingEnd="5dip"
+                android:paddingBottom="30dip"
+                android:paddingRight="50dip"
+                android:layoutDirection="ltr">
+
+            <FrameLayout
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_gravity="top|left"
+                    android:background="#FF0000FF">
+            </FrameLayout>
+
+            <FrameLayout
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_gravity="bottom|right"
+                    android:background="#FF00FF00">
+            </FrameLayout>
+        </FrameLayout>
+
+        <FrameLayout
+                android:layout_width="300dp"
+                android:layout_height="300dp"
+                android:layout_gravity="bottom|left"
+                android:background="#FF888888"
+                android:paddingTop="20dip"
+                android:paddingLeft="40dip"
+                android:paddingStart="5dip"
+                android:paddingBottom="30dip"
+                android:paddingRight="50dip"
+                android:layoutDirection="rtl">
+
+            <FrameLayout
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_gravity="top|left"
+                    android:background="#FF0000FF">
+            </FrameLayout>
+
+            <FrameLayout
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_gravity="bottom|right"
+                    android:background="#FF00FF00">
+            </FrameLayout>
+        </FrameLayout>
+
+        <FrameLayout
+                android:layout_width="300dp"
+                android:layout_height="300dp"
+                android:layout_gravity="bottom|center_horizontal"
+                android:background="#FF888888"
+                android:paddingTop="20dip"
+                android:paddingLeft="40dip"
+                android:paddingEnd="5dip"
+                android:paddingBottom="30dip"
+                android:paddingRight="50dip"
+                android:layoutDirection="rtl">
+
+            <FrameLayout
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_gravity="top|left"
+                    android:background="#FF0000FF">
+            </FrameLayout>
+
+            <FrameLayout
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_gravity="bottom|right"
+                    android:background="#FF00FF00">
+            </FrameLayout>
+        </FrameLayout>
+
+    </FrameLayout>
+
+</FrameLayout>
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
index 0bed7ce..c5e2273 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
@@ -120,6 +120,7 @@
         addItem(result, "Table LOC", BiDiTestTableLayoutLocale.class, R.id.table_layout_locale);
 
         addItem(result, "ViewPadding", BiDiTestViewPadding.class, R.id.view_padding);
+        addItem(result, "ViewPadding MIXED", BiDiTestViewPaddingMixed.class, R.id.view_padding_mixed);
 
         return result;
     }
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestViewPaddingMixed.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestViewPaddingMixed.java
new file mode 100644
index 0000000..7ca2707
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestViewPaddingMixed.java
@@ -0,0 +1,17 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+package com.android.bidi;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class BiDiTestViewPaddingMixed extends Fragment {
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.view_padding_mixed, container, false);
+    }
+}
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index d5dcd4e..c650021 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -31,6 +31,15 @@
         android:hardwareAccelerated="true">
 
         <activity
+                android:name="GetBitmapActivity"
+                android:label="_GetBitmap">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
                 android:name="SmallCircleActivity"
                 android:label="_SmallCircle">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java
new file mode 100644
index 0000000..2e23aaa
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java
@@ -0,0 +1,103 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.Gravity;
+import android.view.TextureView;
+import android.view.View;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class GetBitmapActivity extends Activity implements TextureView.SurfaceTextureListener {
+    private Camera mCamera;
+    private TextureView mTextureView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FrameLayout content = new FrameLayout(this);
+
+        mTextureView = new TextureView(this);
+        mTextureView.setSurfaceTextureListener(this);
+
+        Button button = new Button(this);
+        button.setText("Copy bitmap to /sdcard/textureview.png");
+        button.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Bitmap b = mTextureView.getBitmap();
+                try {
+                    FileOutputStream out = new FileOutputStream(
+                            Environment.getExternalStorageDirectory() + "/textureview.png");
+                    try {
+                        b.compress(Bitmap.CompressFormat.PNG, 100, out);
+                    } finally {
+                        try {
+                            out.close();
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    }
+                } catch (FileNotFoundException e) {
+                    // Ignore
+                }
+            }
+        });
+
+        content.addView(mTextureView, new FrameLayout.LayoutParams(500, 400, Gravity.CENTER));
+        content.addView(button, new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM));
+        setContentView(content);
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        mCamera = Camera.open();
+
+        try {
+            mCamera.setPreviewTexture(surface);
+        } catch (IOException t) {
+            android.util.Log.e("TextureView", "Cannot set preview texture target!", t);
+        }
+
+        mCamera.startPreview();
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        // Ignored, the Camera does all the work for us
+    }
+
+    @Override
+    public void onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        mCamera.stopPreview();
+        mCamera.release();
+    }
+}