Merge "API changes for updating Locale to OpenJDK8u60, part 2"
diff --git a/Android.mk b/Android.mk
index c0bb724..2b565e5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -45,6 +45,7 @@
        core/java/android/app/admin/SecurityLogTags.logtags \
        core/java/android/content/EventLogTags.logtags \
        core/java/android/speech/tts/EventLogTags.logtags \
+       core/java/android/net/EventLogTags.logtags \
        core/java/android/webkit/EventLogTags.logtags \
        core/java/com/android/internal/logging/EventLogTags.logtags \
 
@@ -477,6 +478,7 @@
 
 LOCAL_SRC_FILES +=  \
 	../../system/netd/server/binder/android/net/INetd.aidl \
+	../native/cmds/installd/binder/android/os/IInstalld.aidl \
 
 LOCAL_AIDL_INCLUDES += system/update_engine/binder_bindings
 
@@ -502,7 +504,7 @@
 LOCAL_ADDITIONAL_DEPENDENCIES := $(framework_res_R_stamp)
 
 LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart conscrypt okhttp legacy-test bouncycastle ext
+LOCAL_JAVA_LIBRARIES := core-oj core-libart conscrypt okhttp bouncycastle ext
 LOCAL_STATIC_JAVA_LIBRARIES := framework-protos
 
 LOCAL_MODULE := framework
@@ -729,6 +731,7 @@
 # Search through the base framework dirs for these packages.
 # The result will be relative to frameworks/base.
 fwbase_dirs_to_document := \
+	legacy-test/src \
 	test-runner/src \
 	$(patsubst $(LOCAL_PATH)/%,%, \
 	  $(wildcard \
diff --git a/api/current.txt b/api/current.txt
index 54bbc22..52d0517 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6447,6 +6447,7 @@
   public static class NetworkStats.Bucket {
     ctor public NetworkStats.Bucket();
     method public long getEndTimeStamp();
+    method public int getMetered();
     method public int getRoaming();
     method public long getRxBytes();
     method public long getRxPackets();
@@ -6456,6 +6457,9 @@
     method public long getTxBytes();
     method public long getTxPackets();
     method public int getUid();
+    field public static final int METERED_ALL = -1; // 0xffffffff
+    field public static final int METERED_NO = 1; // 0x1
+    field public static final int METERED_YES = 2; // 0x2
     field public static final int ROAMING_ALL = -1; // 0xffffffff
     field public static final int ROAMING_NO = 1; // 0x1
     field public static final int ROAMING_YES = 2; // 0x2
@@ -62073,13 +62077,13 @@
     ctor public Attributes.Name(java.lang.String);
     field public static final java.util.jar.Attributes.Name CLASS_PATH;
     field public static final java.util.jar.Attributes.Name CONTENT_TYPE;
-    field public static final java.util.jar.Attributes.Name EXTENSION_INSTALLATION;
+    field public static final deprecated java.util.jar.Attributes.Name EXTENSION_INSTALLATION;
     field public static final java.util.jar.Attributes.Name EXTENSION_LIST;
     field public static final java.util.jar.Attributes.Name EXTENSION_NAME;
     field public static final java.util.jar.Attributes.Name IMPLEMENTATION_TITLE;
-    field public static final java.util.jar.Attributes.Name IMPLEMENTATION_URL;
+    field public static final deprecated java.util.jar.Attributes.Name IMPLEMENTATION_URL;
     field public static final java.util.jar.Attributes.Name IMPLEMENTATION_VENDOR;
-    field public static final java.util.jar.Attributes.Name IMPLEMENTATION_VENDOR_ID;
+    field public static final deprecated java.util.jar.Attributes.Name IMPLEMENTATION_VENDOR_ID;
     field public static final java.util.jar.Attributes.Name IMPLEMENTATION_VERSION;
     field public static final java.util.jar.Attributes.Name MAIN_CLASS;
     field public static final java.util.jar.Attributes.Name MANIFEST_VERSION;
@@ -62146,11 +62150,11 @@
   }
 
   public static abstract interface Pack200.Packer {
-    method public abstract void addPropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void addPropertyChangeListener(java.beans.PropertyChangeListener);
     method public abstract void pack(java.util.jar.JarFile, java.io.OutputStream) throws java.io.IOException;
     method public abstract void pack(java.util.jar.JarInputStream, java.io.OutputStream) throws java.io.IOException;
     method public abstract java.util.SortedMap<java.lang.String, java.lang.String> properties();
-    method public abstract void removePropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void removePropertyChangeListener(java.beans.PropertyChangeListener);
     field public static final java.lang.String CLASS_ATTRIBUTE_PFX = "pack.class.attribute.";
     field public static final java.lang.String CODE_ATTRIBUTE_PFX = "pack.code.attribute.";
     field public static final java.lang.String DEFLATE_HINT = "pack.deflate.hint";
@@ -62173,9 +62177,9 @@
   }
 
   public static abstract interface Pack200.Unpacker {
-    method public abstract void addPropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void addPropertyChangeListener(java.beans.PropertyChangeListener);
     method public abstract java.util.SortedMap<java.lang.String, java.lang.String> properties();
-    method public abstract void removePropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void removePropertyChangeListener(java.beans.PropertyChangeListener);
     method public abstract void unpack(java.io.InputStream, java.util.jar.JarOutputStream) throws java.io.IOException;
     method public abstract void unpack(java.io.File, java.util.jar.JarOutputStream) throws java.io.IOException;
     field public static final java.lang.String DEFLATE_HINT = "unpack.deflate.hint";
diff --git a/api/system-current.txt b/api/system-current.txt
index a0aebfc..5041eac 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6739,6 +6739,7 @@
   public static class NetworkStats.Bucket {
     ctor public NetworkStats.Bucket();
     method public long getEndTimeStamp();
+    method public int getMetered();
     method public int getRoaming();
     method public long getRxBytes();
     method public long getRxPackets();
@@ -6748,6 +6749,9 @@
     method public long getTxBytes();
     method public long getTxPackets();
     method public int getUid();
+    field public static final int METERED_ALL = -1; // 0xffffffff
+    field public static final int METERED_NO = 1; // 0x1
+    field public static final int METERED_YES = 2; // 0x2
     field public static final int ROAMING_ALL = -1; // 0xffffffff
     field public static final int ROAMING_NO = 1; // 0x1
     field public static final int ROAMING_YES = 2; // 0x2
@@ -65634,13 +65638,13 @@
     ctor public Attributes.Name(java.lang.String);
     field public static final java.util.jar.Attributes.Name CLASS_PATH;
     field public static final java.util.jar.Attributes.Name CONTENT_TYPE;
-    field public static final java.util.jar.Attributes.Name EXTENSION_INSTALLATION;
+    field public static final deprecated java.util.jar.Attributes.Name EXTENSION_INSTALLATION;
     field public static final java.util.jar.Attributes.Name EXTENSION_LIST;
     field public static final java.util.jar.Attributes.Name EXTENSION_NAME;
     field public static final java.util.jar.Attributes.Name IMPLEMENTATION_TITLE;
-    field public static final java.util.jar.Attributes.Name IMPLEMENTATION_URL;
+    field public static final deprecated java.util.jar.Attributes.Name IMPLEMENTATION_URL;
     field public static final java.util.jar.Attributes.Name IMPLEMENTATION_VENDOR;
-    field public static final java.util.jar.Attributes.Name IMPLEMENTATION_VENDOR_ID;
+    field public static final deprecated java.util.jar.Attributes.Name IMPLEMENTATION_VENDOR_ID;
     field public static final java.util.jar.Attributes.Name IMPLEMENTATION_VERSION;
     field public static final java.util.jar.Attributes.Name MAIN_CLASS;
     field public static final java.util.jar.Attributes.Name MANIFEST_VERSION;
@@ -65707,11 +65711,11 @@
   }
 
   public static abstract interface Pack200.Packer {
-    method public abstract void addPropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void addPropertyChangeListener(java.beans.PropertyChangeListener);
     method public abstract void pack(java.util.jar.JarFile, java.io.OutputStream) throws java.io.IOException;
     method public abstract void pack(java.util.jar.JarInputStream, java.io.OutputStream) throws java.io.IOException;
     method public abstract java.util.SortedMap<java.lang.String, java.lang.String> properties();
-    method public abstract void removePropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void removePropertyChangeListener(java.beans.PropertyChangeListener);
     field public static final java.lang.String CLASS_ATTRIBUTE_PFX = "pack.class.attribute.";
     field public static final java.lang.String CODE_ATTRIBUTE_PFX = "pack.code.attribute.";
     field public static final java.lang.String DEFLATE_HINT = "pack.deflate.hint";
@@ -65734,9 +65738,9 @@
   }
 
   public static abstract interface Pack200.Unpacker {
-    method public abstract void addPropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void addPropertyChangeListener(java.beans.PropertyChangeListener);
     method public abstract java.util.SortedMap<java.lang.String, java.lang.String> properties();
-    method public abstract void removePropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void removePropertyChangeListener(java.beans.PropertyChangeListener);
     method public abstract void unpack(java.io.InputStream, java.util.jar.JarOutputStream) throws java.io.IOException;
     method public abstract void unpack(java.io.File, java.util.jar.JarOutputStream) throws java.io.IOException;
     field public static final java.lang.String DEFLATE_HINT = "unpack.deflate.hint";
diff --git a/api/test-current.txt b/api/test-current.txt
index 0747aa3..74450e6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6456,6 +6456,7 @@
   public static class NetworkStats.Bucket {
     ctor public NetworkStats.Bucket();
     method public long getEndTimeStamp();
+    method public int getMetered();
     method public int getRoaming();
     method public long getRxBytes();
     method public long getRxPackets();
@@ -6465,6 +6466,9 @@
     method public long getTxBytes();
     method public long getTxPackets();
     method public int getUid();
+    field public static final int METERED_ALL = -1; // 0xffffffff
+    field public static final int METERED_NO = 1; // 0x1
+    field public static final int METERED_YES = 2; // 0x2
     field public static final int ROAMING_ALL = -1; // 0xffffffff
     field public static final int ROAMING_NO = 1; // 0x1
     field public static final int ROAMING_YES = 2; // 0x2
@@ -62164,13 +62168,13 @@
     ctor public Attributes.Name(java.lang.String);
     field public static final java.util.jar.Attributes.Name CLASS_PATH;
     field public static final java.util.jar.Attributes.Name CONTENT_TYPE;
-    field public static final java.util.jar.Attributes.Name EXTENSION_INSTALLATION;
+    field public static final deprecated java.util.jar.Attributes.Name EXTENSION_INSTALLATION;
     field public static final java.util.jar.Attributes.Name EXTENSION_LIST;
     field public static final java.util.jar.Attributes.Name EXTENSION_NAME;
     field public static final java.util.jar.Attributes.Name IMPLEMENTATION_TITLE;
-    field public static final java.util.jar.Attributes.Name IMPLEMENTATION_URL;
+    field public static final deprecated java.util.jar.Attributes.Name IMPLEMENTATION_URL;
     field public static final java.util.jar.Attributes.Name IMPLEMENTATION_VENDOR;
-    field public static final java.util.jar.Attributes.Name IMPLEMENTATION_VENDOR_ID;
+    field public static final deprecated java.util.jar.Attributes.Name IMPLEMENTATION_VENDOR_ID;
     field public static final java.util.jar.Attributes.Name IMPLEMENTATION_VERSION;
     field public static final java.util.jar.Attributes.Name MAIN_CLASS;
     field public static final java.util.jar.Attributes.Name MANIFEST_VERSION;
@@ -62237,11 +62241,11 @@
   }
 
   public static abstract interface Pack200.Packer {
-    method public abstract void addPropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void addPropertyChangeListener(java.beans.PropertyChangeListener);
     method public abstract void pack(java.util.jar.JarFile, java.io.OutputStream) throws java.io.IOException;
     method public abstract void pack(java.util.jar.JarInputStream, java.io.OutputStream) throws java.io.IOException;
     method public abstract java.util.SortedMap<java.lang.String, java.lang.String> properties();
-    method public abstract void removePropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void removePropertyChangeListener(java.beans.PropertyChangeListener);
     field public static final java.lang.String CLASS_ATTRIBUTE_PFX = "pack.class.attribute.";
     field public static final java.lang.String CODE_ATTRIBUTE_PFX = "pack.code.attribute.";
     field public static final java.lang.String DEFLATE_HINT = "pack.deflate.hint";
@@ -62264,9 +62268,9 @@
   }
 
   public static abstract interface Pack200.Unpacker {
-    method public abstract void addPropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void addPropertyChangeListener(java.beans.PropertyChangeListener);
     method public abstract java.util.SortedMap<java.lang.String, java.lang.String> properties();
-    method public abstract void removePropertyChangeListener(java.beans.PropertyChangeListener);
+    method public default deprecated void removePropertyChangeListener(java.beans.PropertyChangeListener);
     method public abstract void unpack(java.io.InputStream, java.util.jar.JarOutputStream) throws java.io.IOException;
     method public abstract void unpack(java.io.File, java.util.jar.JarOutputStream) throws java.io.IOException;
     field public static final java.lang.String DEFLATE_HINT = "unpack.deflate.hint";
diff --git a/core/java/android/app/usage/NetworkStats.java b/core/java/android/app/usage/NetworkStats.java
index 226aa8f..3670b91 100644
--- a/core/java/android/app/usage/NetworkStats.java
+++ b/core/java/android/app/usage/NetworkStats.java
@@ -164,6 +164,29 @@
         public static final int UID_TETHERING = TrafficStats.UID_TETHERING;
 
         /** @hide */
+        @IntDef({METERED_ALL, METERED_NO, METERED_YES})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Metered {}
+
+        /**
+         * Combined usage across all metered states. Covers metered and unmetered usage.
+         */
+        public static final int METERED_ALL = -1;
+
+        /**
+         * Usage that occurs on an unmetered network.
+         */
+        public static final int METERED_NO = 0x1;
+
+        /**
+         * Usage that occurs on a metered network.
+         *
+         * <p>A network is classified as metered when the user is sensitive to heavy data usage on
+         * that connection.
+         */
+        public static final int METERED_YES = 0x2;
+
+        /** @hide */
         @IntDef({ROAMING_ALL, ROAMING_NO, ROAMING_YES})
         @Retention(RetentionPolicy.SOURCE)
         public @interface Roaming {}
@@ -200,6 +223,7 @@
         private int mUid;
         private int mTag;
         private int mState;
+        private int mMetered;
         private int mRoaming;
         private long mBeginTimeStamp;
         private long mEndTimeStamp;
@@ -232,6 +256,15 @@
             return tag;
         }
 
+        private static @Metered int convertMetered(int metered) {
+            switch (metered) {
+                case android.net.NetworkStats.METERED_ALL : return METERED_ALL;
+                case android.net.NetworkStats.METERED_NO: return METERED_NO;
+                case android.net.NetworkStats.METERED_YES: return METERED_YES;
+            }
+            return 0;
+        }
+
         private static @Roaming int convertRoaming(int roaming) {
             switch (roaming) {
                 case android.net.NetworkStats.ROAMING_ALL : return ROAMING_ALL;
@@ -279,6 +312,21 @@
         }
 
         /**
+         * Metered state. One of the following values:<p/>
+         * <ul>
+         * <li>{@link #METERED_ALL}</li>
+         * <li>{@link #METERED_NO}</li>
+         * <li>{@link #METERED_YES}</li>
+         * </ul>
+         * <p>A network is classified as metered when the user is sensitive to heavy data usage on
+         * that connection. Apps may warn before using these networks for large downloads. The
+         * metered state can be set by the user within data usage network restrictions.
+         */
+        public @Metered int getMetered() {
+            return mMetered;
+        }
+
+        /**
          * Roaming state. One of the following values:<p/>
          * <ul>
          * <li>{@link #ROAMING_ALL}</li>
@@ -491,6 +539,7 @@
         bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid);
         bucketOut.mTag = Bucket.convertTag(mRecycledSummaryEntry.tag);
         bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set);
+        bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered);
         bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming);
         bucketOut.mBeginTimeStamp = mStartTimeStamp;
         bucketOut.mEndTimeStamp = mEndTimeStamp;
@@ -539,6 +588,7 @@
                 bucketOut.mUid = Bucket.convertUid(getUid());
                 bucketOut.mTag = Bucket.convertTag(mTag);
                 bucketOut.mState = Bucket.STATE_ALL;
+                bucketOut.mMetered = Bucket.METERED_ALL;
                 bucketOut.mRoaming = Bucket.ROAMING_ALL;
                 bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart;
                 bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart +
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 7961a72..840413a 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -51,16 +51,17 @@
  * {@link #querySummaryForUser} <p />
  * {@link #querySummary} <p />
  * These queries aggregate network usage across the whole interval. Therefore there will be only one
- * bucket for a particular key and state and roaming combination. In case of the user-wide and
- * device-wide summaries a single bucket containing the totalised network usage is returned.
+ * bucket for a particular key, state, metered and roaming combination. In case of the user-wide
+ * and device-wide summaries a single bucket containing the totalised network usage is returned.
  * <h3>
  * History queries
  * </h3>
  * {@link #queryDetailsForUid} <p />
  * {@link #queryDetails} <p />
- * These queries do not aggregate over time but do aggregate over state and roaming. Therefore there
- * can be multiple buckets for a particular key but all Bucket's state is going to be
- * {@link NetworkStats.Bucket#STATE_ALL} and all Bucket's roaming is going to be
+ * These queries do not aggregate over time but do aggregate over state, metered and roaming.
+ * Therefore there can be multiple buckets for a particular key but all Bucket's state is going to
+ * be {@link NetworkStats.Bucket#STATE_ALL}, all Bucket's metered is going to be
+ * {@link NetworkStats.Bucket#METERED_ALL}, and all Bucket's roaming is going to be
  * {@link NetworkStats.Bucket#ROAMING_ALL}.
  * <p />
  * <b>NOTE:</b> Calling {@link #querySummaryForDevice} or accessing stats for apps other than the
@@ -103,10 +104,11 @@
 
     /**
      * Query network usage statistics summaries. Result is summarised data usage for the whole
-     * device. Result is a single Bucket aggregated over time, state, uid, tag and roaming. This
-     * means the bucket's start and end timestamp are going to be the same as the 'startTime' and
-     * 'endTime' parameters. State is going to be {@link NetworkStats.Bucket#STATE_ALL}, uid
-     * {@link NetworkStats.Bucket#UID_ALL}, tag {@link NetworkStats.Bucket#TAG_NONE}
+     * device. Result is a single Bucket aggregated over time, state, uid, tag, metered, and
+     * roaming. This means the bucket's start and end timestamp are going to be the same as the
+     * 'startTime' and 'endTime' parameters. State is going to be
+     * {@link NetworkStats.Bucket#STATE_ALL}, uid {@link NetworkStats.Bucket#UID_ALL},
+     * tag {@link NetworkStats.Bucket#TAG_NONE}, metered {@link NetworkStats.Bucket#METERED_ALL},
      * and roaming {@link NetworkStats.Bucket#ROAMING_ALL}.
      *
      * @param networkType As defined in {@link ConnectivityManager}, e.g.
@@ -142,8 +144,10 @@
      * Query network usage statistics summaries. Result is summarised data usage for all uids
      * belonging to calling user. Result is a single Bucket aggregated over time, state and uid.
      * This means the bucket's start and end timestamp are going to be the same as the 'startTime'
-     * and 'endTime' parameters, state is going to be {@link NetworkStats.Bucket#STATE_ALL} and uid
-     * {@link NetworkStats.Bucket#UID_ALL}.
+     * and 'endTime' parameters. State is going to be {@link NetworkStats.Bucket#STATE_ALL},
+     * uid {@link NetworkStats.Bucket#UID_ALL}, tag {@link NetworkStats.Bucket#TAG_NONE},
+     * metered {@link NetworkStats.Bucket#METERED_ALL}, and roaming
+     * {@link NetworkStats.Bucket#ROAMING_ALL}.
      *
      * @param networkType As defined in {@link ConnectivityManager}, e.g.
      *            {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
@@ -177,9 +181,10 @@
     /**
      * Query network usage statistics summaries. Result filtered to include only uids belonging to
      * calling user. Result is aggregated over time, hence all buckets will have the same start and
-     * end timestamps. Not aggregated over state or uid. This means buckets' start and end
-     * timestamps are going to be the same as the 'startTime' and 'endTime' parameters.
-     * State and uid are going to vary, and tag is going to be the same.
+     * end timestamps. State is going to be {@link NetworkStats.Bucket#STATE_ALL},
+     * uid {@link NetworkStats.Bucket#UID_ALL}, tag {@link NetworkStats.Bucket#TAG_NONE},
+     * metered {@link NetworkStats.Bucket#METERED_ALL}, and roaming
+     * {@link NetworkStats.Bucket#ROAMING_ALL}.
      *
      * @param networkType As defined in {@link ConnectivityManager}, e.g.
      *            {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
@@ -262,10 +267,12 @@
 
     /**
      * Query network usage statistics details. Result filtered to include only uids belonging to
-     * calling user. Result is aggregated over state but not aggregated over time or uid. This means
-     * buckets' start and end timestamps are going to be between 'startTime' and 'endTime'
-     * parameters. State is going to be {@link NetworkStats.Bucket#STATE_ALL}, uid will vary,
-     * tag {@link NetworkStats.Bucket#TAG_NONE} and roaming is going to be
+     * calling user. Result is aggregated over state but not aggregated over time, uid, tag,
+     * metered, nor roaming. This means buckets' start and end timestamps are going to be between
+     * 'startTime' and 'endTime' parameters. State is going to be
+     * {@link NetworkStats.Bucket#STATE_ALL}, uid will vary,
+     * tag {@link NetworkStats.Bucket#TAG_NONE}, metered is going to be
+     * {@link NetworkStats.Bucket#METERED_ALL}, and roaming is going to be
      * {@link NetworkStats.Bucket#ROAMING_ALL}.
      * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
      * interpolate across partial buckets. Since bucket length is in the order of hours, this
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index d419d03..61544e4 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -23,6 +23,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
+import android.app.ActivityThread;
 import android.bluetooth.le.BluetoothLeAdvertiser;
 import android.bluetooth.le.BluetoothLeScanner;
 import android.bluetooth.le.ScanCallback;
@@ -255,6 +256,29 @@
             "android.bluetooth.adapter.action.REQUEST_ENABLE";
 
     /**
+     * Activity Action: Show a system activity that allows the user to turn off
+     * Bluetooth. This is used only if permission review is enabled which is for
+     * apps targeting API less than 23 require a permission review before any of
+     * the app's components can run.
+     * <p>This system activity will return once Bluetooth has completed turning
+     * off, or the user has decided not to turn Bluetooth off.
+     * <p>Notification of the result of this activity is posted using the
+     * {@link android.app.Activity#onActivityResult} callback. The
+     * <code>resultCode</code>
+     * will be {@link android.app.Activity#RESULT_OK} if Bluetooth has been
+     * turned off or {@link android.app.Activity#RESULT_CANCELED} if the user
+     * has rejected the request or an error has occurred.
+     * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED}
+     * for global notification whenever Bluetooth is turned on or off.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_REQUEST_DISABLE =
+            "android.bluetooth.adapter.action.REQUEST_DISABLE";
+
+    /**
      * Activity Action: Show a system activity that allows user to enable BLE scans even when
      * Bluetooth is turned off.<p>
      *
@@ -768,7 +792,7 @@
                 return true;
             }
             if (DBG) Log.d(TAG, "enableBLE(): Calling enable");
-            return mManagerService.enable();
+            return mManagerService.enable(ActivityThread.currentPackageName());
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -895,7 +919,7 @@
             return true;
         }
         try {
-            return mManagerService.enable();
+            return mManagerService.enable(ActivityThread.currentPackageName());
         } catch (RemoteException e) {Log.e(TAG, "", e);}
         return false;
     }
@@ -927,7 +951,7 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
     public boolean disable() {
         try {
-            return mManagerService.disable(true);
+            return mManagerService.disable(ActivityThread.currentPackageName(), true);
         } catch (RemoteException e) {Log.e(TAG, "", e);}
         return false;
     }
@@ -945,7 +969,7 @@
     public boolean disable(boolean persist) {
 
         try {
-            return mManagerService.disable(persist);
+            return mManagerService.disable(ActivityThread.currentPackageName(), persist);
         } catch (RemoteException e) {Log.e(TAG, "", e);}
         return false;
     }
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
index 2b853a3..2ab9ae8 100644
--- a/core/java/android/bluetooth/IBluetoothManager.aidl
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -34,9 +34,9 @@
     void registerStateChangeCallback(in IBluetoothStateChangeCallback callback);
     void unregisterStateChangeCallback(in IBluetoothStateChangeCallback callback);
     boolean isEnabled();
-    boolean enable();
+    boolean enable(String packageName);
     boolean enableNoAutoConnect();
-    boolean disable(boolean persist);
+    boolean disable( String packageName, boolean persist);
     int getState();
     IBluetoothGatt getBluetoothGatt();
 
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 3df57bc..ee646c0 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -181,6 +181,14 @@
     public static final String USB_DATA_UNLOCKED = "unlocked";
 
     /**
+     * Boolean extra indicating whether the intent represents a change in the usb
+     * configuration (as opposed to a state update).
+     *
+     * {@hide}
+     */
+    public static final String USB_CONFIG_CHANGED = "config_changed";
+
+    /**
      * A placeholder indicating that no USB function is being specified.
      * Used to distinguish between selecting no function vs. the default function in
      * {@link #setCurrentFunction(String)}.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 43c8c81..51431eb 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2079,6 +2079,8 @@
     @SystemApi
     public void startTethering(int type, boolean showProvisioningUi,
             final OnStartTetheringCallback callback, Handler handler) {
+        checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
+
         ResultReceiver wrappedCallback = new ResultReceiver(handler) {
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -2089,6 +2091,7 @@
                 }
             }
         };
+
         try {
             mService.startTethering(type, wrappedCallback, showProvisioningUi);
         } catch (RemoteException e) {
@@ -2657,6 +2660,7 @@
     public static final int CALLBACK_IP_CHANGED          = BASE + 7;
     /** @hide */
     public static final int CALLBACK_RELEASED            = BASE + 8;
+    // TODO: consider deleting CALLBACK_EXIT and shifting following enum codes down by 1.
     /** @hide */
     public static final int CALLBACK_EXIT                = BASE + 9;
     /** @hide obj = NetworkCapabilities, arg1 = seq number */
@@ -2687,24 +2691,17 @@
     }
 
     private class CallbackHandler extends Handler {
-        private final HashMap<NetworkRequest, NetworkCallback>mCallbackMap;
-        private final AtomicInteger mRefCount;
         private static final String TAG = "ConnectivityManager.CallbackHandler";
-        private final ConnectivityManager mCm;
         private static final boolean DBG = false;
 
-        CallbackHandler(Looper looper, HashMap<NetworkRequest, NetworkCallback>callbackMap,
-                AtomicInteger refCount, ConnectivityManager cm) {
+        CallbackHandler(Looper looper) {
             super(looper);
-            mCallbackMap = callbackMap;
-            mRefCount = refCount;
-            mCm = cm;
         }
 
         @Override
         public void handleMessage(Message message) {
-            NetworkRequest request = (NetworkRequest) getObject(message, NetworkRequest.class);
-            Network network = (Network) getObject(message, Network.class);
+            NetworkRequest request = getObject(message, NetworkRequest.class);
+            Network network = getObject(message, Network.class);
             if (DBG) {
                 Log.d(TAG, whatToString(message.what) + " for network " + network);
             }
@@ -2747,9 +2744,7 @@
                 case CALLBACK_CAP_CHANGED: {
                     NetworkCallback callback = getCallback(request, "CAP_CHANGED");
                     if (callback != null) {
-                        NetworkCapabilities cap = (NetworkCapabilities)getObject(message,
-                                NetworkCapabilities.class);
-
+                        NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
                         callback.onCapabilitiesChanged(network, cap);
                     }
                     break;
@@ -2757,9 +2752,7 @@
                 case CALLBACK_IP_CHANGED: {
                     NetworkCallback callback = getCallback(request, "IP_CHANGED");
                     if (callback != null) {
-                        LinkProperties lp = (LinkProperties)getObject(message,
-                                LinkProperties.class);
-
+                        LinkProperties lp = getObject(message, LinkProperties.class);
                         callback.onLinkPropertiesChanged(network, lp);
                     }
                     break;
@@ -2779,24 +2772,16 @@
                     break;
                 }
                 case CALLBACK_RELEASED: {
-                    NetworkCallback callback = null;
-                    synchronized(mCallbackMap) {
-                        callback = mCallbackMap.remove(request);
+                    final NetworkCallback callback;
+                    synchronized(sCallbacks) {
+                        callback = sCallbacks.remove(request);
                     }
-                    if (callback != null) {
-                        synchronized(mRefCount) {
-                            if (mRefCount.decrementAndGet() == 0) {
-                                getLooper().quit();
-                            }
-                        }
-                    } else {
+                    if (callback == null) {
                         Log.e(TAG, "callback not found for RELEASED message");
                     }
                     break;
                 }
                 case CALLBACK_EXIT: {
-                    Log.d(TAG, "Listener quitting");
-                    getLooper().quit();
                     break;
                 }
                 case EXPIRE_LEGACY_REQUEST: {
@@ -2806,14 +2791,14 @@
             }
         }
 
-        private Object getObject(Message msg, Class c) {
-            return msg.getData().getParcelable(c.getSimpleName());
+        private <T> T getObject(Message msg, Class<T> c) {
+            return (T) msg.getData().getParcelable(c.getSimpleName());
         }
 
         private NetworkCallback getCallback(NetworkRequest req, String name) {
             NetworkCallback callback;
-            synchronized(mCallbackMap) {
-                callback = mCallbackMap.get(req);
+            synchronized(sCallbacks) {
+                callback = sCallbacks.get(req);
             }
             if (callback == null) {
                 Log.e(TAG, "callback not found for " + name + " message");
@@ -2822,63 +2807,56 @@
         }
     }
 
-    private void incCallbackHandlerRefCount() {
-        synchronized(sCallbackRefCount) {
-            if (sCallbackRefCount.incrementAndGet() == 1) {
-                // TODO: switch this to ConnectivityThread
-                HandlerThread callbackThread = new HandlerThread("ConnectivityManager");
-                callbackThread.start();
-                sCallbackHandler = new CallbackHandler(callbackThread.getLooper(),
-                        sNetworkCallback, sCallbackRefCount, this);
+    private CallbackHandler getHandler() {
+        synchronized (sCallbacks) {
+            if (sCallbackHandler == null) {
+                sCallbackHandler = new CallbackHandler(ConnectivityThread.getInstanceLooper());
             }
+            return sCallbackHandler;
         }
     }
 
-    private void decCallbackHandlerRefCount() {
-        synchronized(sCallbackRefCount) {
-            if (sCallbackRefCount.decrementAndGet() == 0) {
-                sCallbackHandler.obtainMessage(CALLBACK_EXIT).sendToTarget();
-                sCallbackHandler = null;
-            }
-        }
-    }
-
-    static final HashMap<NetworkRequest, NetworkCallback> sNetworkCallback =
-            new HashMap<NetworkRequest, NetworkCallback>();
-    static final AtomicInteger sCallbackRefCount = new AtomicInteger(0);
-    static CallbackHandler sCallbackHandler = null;
+    static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>();
+    static CallbackHandler sCallbackHandler;
 
     private final static int LISTEN  = 1;
     private final static int REQUEST = 2;
 
     private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
-            NetworkCallback networkCallback, int timeoutMs, int action,
-            int legacyType) {
-        if (networkCallback == null) {
+            NetworkCallback callback, int timeoutMs, int action, int legacyType) {
+        return sendRequestForNetwork(need, callback, getHandler(), timeoutMs, action, legacyType);
+    }
+
+    private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
+            NetworkCallback callback, Handler handler, int timeoutMs, int action, int legacyType) {
+        if (callback == null) {
             throw new IllegalArgumentException("null NetworkCallback");
         }
         if (need == null && action != REQUEST) {
             throw new IllegalArgumentException("null NetworkCapabilities");
         }
+        // TODO: throw an exception if callback.networkRequest is not null.
+        // http://b/20701525
+        final NetworkRequest request;
         try {
-            incCallbackHandlerRefCount();
-            synchronized(sNetworkCallback) {
+            synchronized(sCallbacks) {
+                Messenger messenger = new Messenger(handler);
+                Binder binder = new Binder();
                 if (action == LISTEN) {
-                    networkCallback.networkRequest = mService.listenForNetwork(need,
-                            new Messenger(sCallbackHandler), new Binder());
+                    request = mService.listenForNetwork(need, messenger, binder);
                 } else {
-                    networkCallback.networkRequest = mService.requestNetwork(need,
-                            new Messenger(sCallbackHandler), timeoutMs, new Binder(), legacyType);
+                    request = mService.requestNetwork(
+                            need, messenger, timeoutMs, binder, legacyType);
                 }
-                if (networkCallback.networkRequest != null) {
-                    sNetworkCallback.put(networkCallback.networkRequest, networkCallback);
+                if (request != null) {
+                    sCallbacks.put(request, callback);
                 }
+                callback.networkRequest = request;
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        if (networkCallback.networkRequest == null) decCallbackHandlerRefCount();
-        return networkCallback.networkRequest;
+        return request;
     }
 
     /**
diff --git a/core/java/android/net/ConnectivityThread.java b/core/java/android/net/ConnectivityThread.java
index 55c3402..0b218e7 100644
--- a/core/java/android/net/ConnectivityThread.java
+++ b/core/java/android/net/ConnectivityThread.java
@@ -27,25 +27,30 @@
  * @hide
  */
 public final class ConnectivityThread extends HandlerThread {
-    private static ConnectivityThread sInstance;
+
+    // A class implementing the lazy holder idiom: the unique static instance
+    // of ConnectivityThread is instantiated in a thread-safe way (guaranteed by
+    // the language specs) the first time that Singleton is referenced in get()
+    // or getInstanceLooper().
+    private static class Singleton {
+        private static final ConnectivityThread INSTANCE = createInstance();
+    }
 
     private ConnectivityThread() {
         super("ConnectivityThread");
     }
 
-    private static synchronized ConnectivityThread getInstance() {
-        if (sInstance == null) {
-            sInstance = new ConnectivityThread();
-            sInstance.start();
-        }
-        return sInstance;
+    private static ConnectivityThread createInstance() {
+        ConnectivityThread t = new ConnectivityThread();
+        t.start();
+        return t;
     }
 
     public static ConnectivityThread get() {
-        return getInstance();
+        return Singleton.INSTANCE;
     }
 
     public static Looper getInstanceLooper() {
-        return getInstance().getLooper();
+        return Singleton.INSTANCE.getLooper();
     }
 }
diff --git a/core/java/android/net/EventLogTags.logtags b/core/java/android/net/EventLogTags.logtags
new file mode 100644
index 0000000..d5ed014
--- /dev/null
+++ b/core/java/android/net/EventLogTags.logtags
@@ -0,0 +1,6 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package android.net
+
+50080 ntp_success (server|3),(rtt|2),(offset|2)
+50081 ntp_failure (server|3),(msg|3)
diff --git a/core/java/android/net/IIpConnectivityMetrics.aidl b/core/java/android/net/IIpConnectivityMetrics.aidl
index 8f634bb..d36b766 100644
--- a/core/java/android/net/IIpConnectivityMetrics.aidl
+++ b/core/java/android/net/IIpConnectivityMetrics.aidl
@@ -23,7 +23,8 @@
 interface IIpConnectivityMetrics {
 
     /**
-     * @return number of remaining available slots in buffer.
+     * @return the number of remaining available slots in buffer,
+     * or -1 if the event was dropped due to rate limiting.
      */
     int logEvent(in ConnectivityMetricsEvent event);
 }
diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl
index 542a0a7..24f4504 100644
--- a/core/java/android/net/INetworkScoreService.aidl
+++ b/core/java/android/net/INetworkScoreService.aidl
@@ -58,12 +58,14 @@
     /**
      * Register a cache to receive scoring updates.
      *
-     * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
-     * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
-     * @throws SecurityException if the caller is not the system.
+     * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}
+     * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores
+     * @param filterType the {@link CacheUpdateFilter} to apply
+     * @throws SecurityException if the caller is not the system
+     * @throws IllegalArgumentException if a score cache is already registed for this type
      * @hide
      */
-    void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache);
+    void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache, int filterType);
 
     /**
      * Unregister a cache to receive scoring updates.
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index d570e66..c704ef0 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -171,7 +171,8 @@
         String subscriberId = null;
         String networkId = null;
         boolean roaming = false;
-        boolean metered = false;
+        boolean metered = !state.networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
 
         if (isNetworkTypeMobile(type)) {
             if (state.subscriberId == null) {
@@ -185,9 +186,6 @@
             subscriberId = state.subscriberId;
             roaming = state.networkInfo.isRoaming();
 
-            metered = !state.networkCapabilities.hasCapability(
-                    NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-
         } else if (type == TYPE_WIFI) {
             if (state.networkId != null) {
                 networkId = state.networkId;
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index af21cef..e08767c 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -28,6 +29,9 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Class that manages communication between network subsystems and a network scorer.
  *
@@ -131,6 +135,29 @@
      */
     public static final String EXTRA_NEW_SCORER = "newScorer";
 
+    /** @hide */
+    @IntDef({CACHE_FILTER_NONE, CACHE_FILTER_CURRENT_NETWORK, CACHE_FILTER_SCAN_RESULTS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CacheUpdateFilter {}
+
+    /**
+     * Do not filter updates sent to the cache.
+     * @hide
+     */
+    public static final int CACHE_FILTER_NONE = 0;
+
+    /**
+     * Only send cache updates when the network matches the connected network.
+     * @hide
+     */
+    public static final int CACHE_FILTER_CURRENT_NETWORK = 1;
+
+    /**
+     * Only send cache updates when the network is part of the current scan result set.
+     * @hide
+     */
+    public static final int CACHE_FILTER_SCAN_RESULTS = 2;
+
     private final Context mContext;
     private final INetworkScoreService mService;
 
@@ -268,11 +295,29 @@
      * @throws SecurityException if the caller does not hold the
      *         {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission.
      * @throws IllegalArgumentException if a score cache is already registered for this type.
+     * @deprecated equivalent to registering for cache updates with CACHE_FILTER_NONE.
      * @hide
      */
+    @Deprecated // migrate to registerNetworkScoreCache(int, INetworkScoreCache, int)
     public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+        registerNetworkScoreCache(networkType, scoreCache, CACHE_FILTER_NONE);
+    }
+
+    /**
+     * Register a network score cache.
+     *
+     * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}
+     * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores
+     * @param filterType the {@link CacheUpdateFilter} to apply
+     * @throws SecurityException if the caller does not hold the
+     *         {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission.
+     * @throws IllegalArgumentException if a score cache is already registered for this type.
+     * @hide
+     */
+    public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache,
+            @CacheUpdateFilter int filterType) {
         try {
-            mService.registerNetworkScoreCache(networkType, scoreCache);
+            mService.registerNetworkScoreCache(networkType, scoreCache, filterType);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 25806fa..77ce65b 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -68,11 +68,18 @@
     // TODO: Rename TAG_NONE to TAG_ALL.
     public static final int TAG_NONE = 0;
 
-    /** {@link #set} value for all roaming values. */
+    /** {@link #metered} value to account for all metered states. */
+    public static final int METERED_ALL = -1;
+    /** {@link #metered} value where native, unmetered data is accounted. */
+    public static final int METERED_NO = 0;
+    /** {@link #metered} value where metered data is accounted. */
+    public static final int METERED_YES = 1;
+
+    /** {@link #roaming} value to account for all roaming states. */
     public static final int ROAMING_ALL = -1;
-    /** {@link #set} value where native, non-roaming data is accounted. */
+    /** {@link #roaming} value where native, non-roaming data is accounted. */
     public static final int ROAMING_NO = 0;
-    /** {@link #set} value where roaming data is accounted. */
+    /** {@link #roaming} value where roaming data is accounted. */
     public static final int ROAMING_YES = 1;
 
     // TODO: move fields to "mVariable" notation
@@ -88,6 +95,7 @@
     private int[] uid;
     private int[] set;
     private int[] tag;
+    private int[] metered;
     private int[] roaming;
     private long[] rxBytes;
     private long[] rxPackets;
@@ -105,6 +113,12 @@
          * to disk. We merge in the correct value when reporting this value to clients of
          * getSummary().
          */
+        public int metered;
+        /**
+         * Note that this is only populated w/ the default value when read from /proc or written
+         * to disk. We merge in the correct value when reporting this value to clients of
+         * getSummary().
+         */
         public int roaming;
         public long rxBytes;
         public long rxPackets;
@@ -123,16 +137,17 @@
 
         public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
                 long txBytes, long txPackets, long operations) {
-            this(iface, uid, set, tag, ROAMING_NO, rxBytes, rxPackets, txBytes, txPackets,
-                    operations);
+            this(iface, uid, set, tag, METERED_NO, ROAMING_NO, rxBytes, rxPackets, txBytes,
+                    txPackets, operations);
         }
 
-        public Entry(String iface, int uid, int set, int tag, int roaming, long rxBytes,
-                long rxPackets, long txBytes, long txPackets, long operations) {
+        public Entry(String iface, int uid, int set, int tag, int metered, int roaming,
+                 long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
             this.iface = iface;
             this.uid = uid;
             this.set = set;
             this.tag = tag;
+            this.metered = metered;
             this.roaming = roaming;
             this.rxBytes = rxBytes;
             this.rxPackets = rxPackets;
@@ -165,6 +180,7 @@
             builder.append(" uid=").append(uid);
             builder.append(" set=").append(setToString(set));
             builder.append(" tag=").append(tagToString(tag));
+            builder.append(" metered=").append(meteredToString(metered));
             builder.append(" roaming=").append(roamingToString(roaming));
             builder.append(" rxBytes=").append(rxBytes);
             builder.append(" rxPackets=").append(rxPackets);
@@ -178,13 +194,18 @@
         public boolean equals(Object o) {
             if (o instanceof Entry) {
                 final Entry e = (Entry) o;
-                return uid == e.uid && set == e.set && tag == e.tag && roaming == e.roaming
-                        && rxBytes == e.rxBytes && rxPackets == e.rxPackets && txBytes == e.txBytes
-                        && txPackets == e.txPackets && operations == e.operations
-                        && iface.equals(e.iface);
+                return uid == e.uid && set == e.set && tag == e.tag && metered == e.metered
+                        && roaming == e.roaming && rxBytes == e.rxBytes && rxPackets == e.rxPackets
+                        && txBytes == e.txBytes && txPackets == e.txPackets
+                        && operations == e.operations && iface.equals(e.iface);
             }
             return false;
         }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(uid, set, tag, metered, roaming, iface);
+        }
     }
 
     public NetworkStats(long elapsedRealtime, int initialSize) {
@@ -196,6 +217,7 @@
             this.uid = new int[initialSize];
             this.set = new int[initialSize];
             this.tag = new int[initialSize];
+            this.metered = new int[initialSize];
             this.roaming = new int[initialSize];
             this.rxBytes = new long[initialSize];
             this.rxPackets = new long[initialSize];
@@ -209,6 +231,7 @@
             this.uid = EmptyArray.INT;
             this.set = EmptyArray.INT;
             this.tag = EmptyArray.INT;
+            this.metered = EmptyArray.INT;
             this.roaming = EmptyArray.INT;
             this.rxBytes = EmptyArray.LONG;
             this.rxPackets = EmptyArray.LONG;
@@ -226,6 +249,7 @@
         uid = parcel.createIntArray();
         set = parcel.createIntArray();
         tag = parcel.createIntArray();
+        metered = parcel.createIntArray();
         roaming = parcel.createIntArray();
         rxBytes = parcel.createLongArray();
         rxPackets = parcel.createLongArray();
@@ -243,6 +267,7 @@
         dest.writeIntArray(uid);
         dest.writeIntArray(set);
         dest.writeIntArray(tag);
+        dest.writeIntArray(metered);
         dest.writeIntArray(roaming);
         dest.writeLongArray(rxBytes);
         dest.writeLongArray(rxPackets);
@@ -277,10 +302,11 @@
     }
 
     @VisibleForTesting
-    public NetworkStats addValues(String iface, int uid, int set, int tag, int roaming,
+    public NetworkStats addValues(String iface, int uid, int set, int tag, int metered, int roaming,
             long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
         return addValues(new Entry(
-                iface, uid, set, tag, roaming, rxBytes, rxPackets, txBytes, txPackets, operations));
+                iface, uid, set, tag, metered, roaming, rxBytes, rxPackets, txBytes, txPackets,
+                operations));
     }
 
     /**
@@ -294,6 +320,7 @@
             uid = Arrays.copyOf(uid, newLength);
             set = Arrays.copyOf(set, newLength);
             tag = Arrays.copyOf(tag, newLength);
+            metered = Arrays.copyOf(metered, newLength);
             roaming = Arrays.copyOf(roaming, newLength);
             rxBytes = Arrays.copyOf(rxBytes, newLength);
             rxPackets = Arrays.copyOf(rxPackets, newLength);
@@ -307,6 +334,7 @@
         uid[size] = entry.uid;
         set[size] = entry.set;
         tag[size] = entry.tag;
+        metered[size] = entry.metered;
         roaming[size] = entry.roaming;
         rxBytes[size] = entry.rxBytes;
         rxPackets[size] = entry.rxPackets;
@@ -327,6 +355,7 @@
         entry.uid = uid[i];
         entry.set = set[i];
         entry.tag = tag[i];
+        entry.metered = metered[i];
         entry.roaming = roaming[i];
         entry.rxBytes = rxBytes[i];
         entry.rxPackets = rxPackets[i];
@@ -381,7 +410,8 @@
      * also be used to subtract values from existing rows.
      */
     public NetworkStats combineValues(Entry entry) {
-        final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.roaming);
+        final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered,
+                entry.roaming);
         if (i == -1) {
             // only create new entry when positive contribution
             addValues(entry);
@@ -409,10 +439,11 @@
     /**
      * Find first stats index that matches the requested parameters.
      */
-    public int findIndex(String iface, int uid, int set, int tag, int roaming) {
+    public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming) {
         for (int i = 0; i < size; i++) {
             if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
-                    && roaming == this.roaming[i] && Objects.equals(iface, this.iface[i])) {
+                    && metered == this.metered[i] && roaming == this.roaming[i]
+                    && Objects.equals(iface, this.iface[i])) {
                 return i;
             }
         }
@@ -424,7 +455,7 @@
      * search around the hinted index as an optimization.
      */
     @VisibleForTesting
-    public int findIndexHinted(String iface, int uid, int set, int tag, int roaming,
+    public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming,
             int hintIndex) {
         for (int offset = 0; offset < size; offset++) {
             final int halfOffset = offset / 2;
@@ -438,7 +469,8 @@
             }
 
             if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
-                    && roaming == this.roaming[i] && Objects.equals(iface, this.iface[i])) {
+                    && metered == this.metered[i] && roaming == this.roaming[i]
+                    && Objects.equals(iface, this.iface[i])) {
                 return i;
             }
         }
@@ -452,7 +484,7 @@
      */
     public void spliceOperationsFrom(NetworkStats stats) {
         for (int i = 0; i < size; i++) {
-            final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], roaming[i]);
+            final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], metered[i], roaming[i]);
             if (j == -1) {
                 operations[i] = 0;
             } else {
@@ -542,6 +574,7 @@
         entry.uid = limitUid;
         entry.set = SET_ALL;
         entry.tag = TAG_NONE;
+        entry.metered = METERED_ALL;
         entry.roaming = ROAMING_ALL;
         entry.rxBytes = 0;
         entry.rxPackets = 0;
@@ -637,11 +670,12 @@
             entry.uid = left.uid[i];
             entry.set = left.set[i];
             entry.tag = left.tag[i];
+            entry.metered = left.metered[i];
             entry.roaming = left.roaming[i];
 
             // find remote row that matches, and subtract
             final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag,
-                    entry.roaming, i);
+                    entry.metered, entry.roaming, i);
             if (j == -1) {
                 // newly appearing row, return entire value
                 entry.rxBytes = left.rxBytes[i];
@@ -687,6 +721,7 @@
         entry.uid = UID_ALL;
         entry.set = SET_ALL;
         entry.tag = TAG_NONE;
+        entry.metered = METERED_ALL;
         entry.roaming = ROAMING_ALL;
         entry.operations = 0L;
 
@@ -716,6 +751,7 @@
         entry.iface = IFACE_ALL;
         entry.set = SET_ALL;
         entry.tag = TAG_NONE;
+        entry.metered = METERED_ALL;
         entry.roaming = ROAMING_ALL;
 
         for (int i = 0; i < size; i++) {
@@ -762,6 +798,7 @@
             pw.print(" uid="); pw.print(uid[i]);
             pw.print(" set="); pw.print(setToString(set[i]));
             pw.print(" tag="); pw.print(tagToString(tag[i]));
+            pw.print(" metered="); pw.print(meteredToString(metered[i]));
             pw.print(" roaming="); pw.print(roamingToString(roaming[i]));
             pw.print(" rxBytes="); pw.print(rxBytes[i]);
             pw.print(" rxPackets="); pw.print(rxPackets[i]);
@@ -830,6 +867,22 @@
     }
 
     /**
+     * Return text description of {@link #metered} value.
+     */
+    public static String meteredToString(int metered) {
+        switch (metered) {
+            case METERED_ALL:
+                return "ALL";
+            case METERED_NO:
+                return "NO";
+            case METERED_YES:
+                return "YES";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    /**
      * Return text description of {@link #roaming} value.
      */
     public static String roamingToString(int roaming) {
@@ -904,7 +957,8 @@
         if (pool.isEmpty()) {
             return true;
         }
-        Entry moved = addTrafficToApplications(tunIface,  underlyingIface, tunIfaceTotal, pool);
+        Entry moved =
+                addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool);
         deductTrafficFromVpnApp(tunUid, underlyingIface, moved);
 
         if (!moved.isEmpty()) {
@@ -919,9 +973,9 @@
      * Initializes the data used by the migrateTun() method.
      *
      * This is the first pass iteration which does the following work:
-     * (1) Adds up all the traffic through tun0.
-     * (2) Adds up all the traffic through the tunUid's underlyingIface
+     * (1) Adds up all the traffic through the tunUid's underlyingIface
      *     (both foreground and background).
+     * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
      */
     private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
             Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
@@ -941,8 +995,9 @@
                 underlyingIfaceTotal.add(recycle);
             }
 
-            if (recycle.tag == TAG_NONE && Objects.equals(tunIface, recycle.iface)) {
-                // Add up all tunIface traffic.
+            if (recycle.uid != tunUid && recycle.tag == TAG_NONE
+                    && Objects.equals(tunIface, recycle.iface)) {
+                // Add up all tunIface traffic excluding traffic from the vpn app itself.
                 tunIfaceTotal.add(recycle);
             }
         }
@@ -958,13 +1013,15 @@
         return pool;
     }
 
-    private Entry addTrafficToApplications(String tunIface, String underlyingIface,
+    private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface,
             Entry tunIfaceTotal, Entry pool) {
         Entry moved = new Entry();
         Entry tmpEntry = new Entry();
         tmpEntry.iface = underlyingIface;
         for (int i = 0; i < size; i++) {
-            if (Objects.equals(iface[i], tunIface)) {
+            // the vpn app is excluded from the redistribution but all moved traffic will be
+            // deducted from the vpn app (see deductTrafficFromVpnApp below).
+            if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) {
                 if (tunIfaceTotal.rxBytes > 0) {
                     tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
                 } else {
@@ -994,6 +1051,7 @@
                 tmpEntry.uid = uid[i];
                 tmpEntry.tag = tag[i];
                 tmpEntry.set = set[i];
+                tmpEntry.metered = metered[i];
                 tmpEntry.roaming = roaming[i];
                 combineValues(tmpEntry);
                 if (tag[i] == TAG_NONE) {
@@ -1013,24 +1071,25 @@
         moved.set = SET_DBG_VPN_OUT;
         moved.tag = TAG_NONE;
         moved.iface = underlyingIface;
+        moved.metered = METERED_ALL;
         moved.roaming = ROAMING_ALL;
         combineValues(moved);
 
         // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
         // the TAG_NONE traffic.
         //
-        // Relies on the fact that the underlying traffic only has state ROAMING_NO, which
-        // should be the case as it comes directly from the /proc file. We only blend in the
+        // Relies on the fact that the underlying traffic only has state ROAMING_NO and METERED_NO,
+        // which should be the case as it comes directly from the /proc file. We only blend in the
         // roaming data after applying these adjustments, by checking the NetworkIdentity of the
         // underlying iface.
         int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
-                ROAMING_NO);
+                METERED_NO, ROAMING_NO);
         if (idxVpnBackground != -1) {
             tunSubtract(idxVpnBackground, this, moved);
         }
 
         int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
-                ROAMING_NO);
+                METERED_NO, ROAMING_NO);
         if (idxVpnForeground != -1) {
             tunSubtract(idxVpnForeground, this, moved);
         }
diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java
index 0f3f957..cf81e91 100644
--- a/core/java/android/net/ScoredNetwork.java
+++ b/core/java/android/net/ScoredNetwork.java
@@ -104,7 +104,7 @@
      *     metered.
      */
     public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint) {
-        this(networkKey, rssiCurve, false /* meteredHint */, null /* attributes */);
+        this(networkKey, rssiCurve, meteredHint, null /* attributes */);
     }
 
     /**
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index cf9243f..cea56b5 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -36,8 +36,7 @@
  * }
  * </pre>
  */
-public class SntpClient
-{
+public class SntpClient {
     private static final String TAG = "SntpClient";
     private static final boolean DBG = true;
 
@@ -88,6 +87,7 @@
         try {
             address = InetAddress.getByName(host);
         } catch (Exception e) {
+            EventLogTags.writeNtpFailure(host, e.toString());
             if (DBG) Log.d(TAG, "request time failed: " + e);
             return false;
         }
@@ -142,6 +142,7 @@
             //             = (transit + skew - transit + skew)/2
             //             = (2 * skew)/2 = skew
             long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
+            EventLogTags.writeNtpSuccess(address.toString(), roundTripTime, clockOffset);
             if (DBG) {
                 Log.d(TAG, "round trip: " + roundTripTime + "ms, " +
                         "clock offset: " + clockOffset + "ms");
@@ -153,6 +154,7 @@
             mNtpTimeReference = responseTicks;
             mRoundTripTime = roundTripTime;
         } catch (Exception e) {
+            EventLogTags.writeNtpFailure(address.toString(), e.toString());
             if (DBG) Log.d(TAG, "request time failed: " + e);
             return false;
         } finally {
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index 3b3fa69..0667495 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -42,6 +42,15 @@
     public static final int NETWORK_DISCONNECTED         = 7;
 
     /** {@hide} */
+    public static final int NETWORK_FIRST_VALIDATION_SUCCESS      = 8;
+    /** {@hide} */
+    public static final int NETWORK_REVALIDATION_SUCCESS          = 9;
+    /** {@hide} */
+    public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10;
+    /** {@hide} */
+    public static final int NETWORK_REVALIDATION_PORTAL_FOUND     = 11;
+
+    /** {@hide} */
     @IntDef(value = {
             NETWORK_CONNECTED,
             NETWORK_VALIDATED,
@@ -50,6 +59,10 @@
             NETWORK_LINGER,
             NETWORK_UNLINGER,
             NETWORK_DISCONNECTED,
+            NETWORK_FIRST_VALIDATION_SUCCESS,
+            NETWORK_REVALIDATION_SUCCESS,
+            NETWORK_FIRST_VALIDATION_PORTAL_FOUND,
+            NETWORK_REVALIDATION_PORTAL_FOUND,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventType {}
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 1a31b56..a724ec1 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -44,10 +44,8 @@
     public static final int DNS_FAILURE = 0;
     public static final int DNS_SUCCESS = 1;
 
-    /** {@hide} */
-    @IntDef(value = {PROBE_DNS, PROBE_HTTP, PROBE_HTTPS, PROBE_PAC})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ProbeType {}
+    private static final int FIRST_VALIDATION  = 1 << 8;
+    private static final int REVALIDATION      = 2 << 8;
 
     /** {@hide} */
     @IntDef(value = {DNS_FAILURE, DNS_SUCCESS})
@@ -56,12 +54,17 @@
 
     public final int netId;
     public final long durationMs;
-    public final @ProbeType int probeType;
+    // probeType byte format (MSB to LSB):
+    // byte 0: unused
+    // byte 1: unused
+    // byte 2: 0 = UNKNOWN, 1 = FIRST_VALIDATION, 2 = REVALIDATION
+    // byte 3: PROBE_* constant
+    public final int probeType;
     public final @ReturnCode int returnCode;
 
     /** {@hide} */
     public ValidationProbeEvent(
-            int netId, long durationMs, @ProbeType int probeType, @ReturnCode int returnCode) {
+            int netId, long durationMs, int probeType, @ReturnCode int returnCode) {
         this.netId = netId;
         this.durationMs = durationMs;
         this.probeType = probeType;
@@ -100,8 +103,18 @@
     };
 
     /** @hide */
+    public static int makeProbeType(int probeType, boolean firstValidation) {
+        return (probeType & 0xff) | (firstValidation ? FIRST_VALIDATION : REVALIDATION);
+    }
+
+    /** @hide */
     public static String getProbeName(int probeType) {
-        return Decoder.constants.get(probeType, "PROBE_???");
+        return Decoder.constants.get(probeType & 0xff, "PROBE_???");
+    }
+
+    /** @hide */
+    public static String getValidationStage(int probeType) {
+        return Decoder.constants.get(probeType & 0xff00, "UNKNOWN");
     }
 
     public static void logEvent(int netId, long durationMs, int probeType, int returnCode) {
@@ -109,12 +122,13 @@
 
     @Override
     public String toString() {
-        return String.format("ValidationProbeEvent(%d, %s:%d, %dms)",
-                netId, getProbeName(probeType), returnCode, durationMs);
+        return String.format("ValidationProbeEvent(%d, %s:%d %s, %dms)", netId,
+                getProbeName(probeType), returnCode, getValidationStage(probeType), durationMs);
     }
 
     final static class Decoder {
         static final SparseArray<String> constants = MessageUtils.findMessageNames(
-                new Class[]{ValidationProbeEvent.class}, new String[]{"PROBE_"});
+                new Class[]{ValidationProbeEvent.class},
+                new String[]{"PROBE_", "FIRST_", "REVALIDATION"});
     }
 }
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 86bd502..33d12a3 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -103,11 +103,11 @@
  * to {@link DiscoveryListener#onServiceFound} and a service lost is notified on
  * {@link DiscoveryListener#onServiceLost}.
  *
- * <p> Once the peer application discovers the "Example" http srevice, and needs to receive data
- * from the "Example" application, it can initiate a resolve with {@link #resolveService} to
- * resolve the host and port details for the purpose of establishing a connection. A successful
- * resolve is notified on {@link ResolveListener#onServiceResolved} and a failure is notified
- * on {@link ResolveListener#onResolveFailed}.
+ * <p> Once the peer application discovers the "Example" http service, and either needs to read the
+ * attributes of the service or wants to receive data from the "Example" application, it can
+ * initiate a resolve with {@link #resolveService} to resolve the attributes, host, and port
+ * details. A successful resolve is notified on {@link ResolveListener#onServiceResolved} and a
+ * failure is notified on {@link ResolveListener#onResolveFailed}.
  *
  * Applications can reserve for a service type at
  * http://www.iana.org/form/ports-service. Existing services can be found at
diff --git a/core/java/android/net/nsd/NsdServiceInfo.java b/core/java/android/net/nsd/NsdServiceInfo.java
index 4a06fb1..7b845be 100644
--- a/core/java/android/net/nsd/NsdServiceInfo.java
+++ b/core/java/android/net/nsd/NsdServiceInfo.java
@@ -250,7 +250,8 @@
     }
 
     /**
-     * Retrive attributes as a map of String keys to byte[] values.
+     * Retrieve attributes as a map of String keys to byte[] values. The attributes map is only
+     * valid for a resolved service.
      *
      * <p> The returned map is unmodifiable; changes must be made through {@link #setAttribute} and
      * {@link #removeAttribute}.
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 04fca19..3e0f6da 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -39,4 +39,9 @@
     public ServiceSpecificException(int errorCode) {
         this.errorCode = errorCode;
     }
+
+    @Override
+    public String toString() {
+        return super.toString() + " (code " + errorCode + ")";
+    }
 }
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 3ae28fd..2225296 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -83,6 +83,8 @@
     public static final long TRACE_TAG_DATABASE = 1L << 20;
     /** @hide */
     public static final long TRACE_TAG_NETWORK = 1L << 21;
+    /** @hide */
+    public static final long TRACE_TAG_ADB = 1L << 22;
 
     private static final long TRACE_TAG_NOT_READY = 1L << 63;
     private static final int MAX_SECTION_NAME_LEN = 127;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index db7baac..e52983e 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7172,6 +7172,13 @@
         */
        public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
 
+        /**
+         * Size of the event buffer for IP connectivity metrics.
+         * @hide
+         */
+        public static final String CONNECTIVITY_METRICS_BUFFER_SIZE =
+              "connectivity_metrics_buffer_size";
+
        /** {@hide} */
        public static final String NETSTATS_ENABLED = "netstats_enabled";
        /** {@hide} */
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
deleted file mode 100644
index 419c3d8..0000000
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.internal.os;
-
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Slog;
-
-import com.android.internal.util.Preconditions;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-/**
- * Represents a connection to {@code installd}. Allows multiple connect and
- * disconnect cycles.
- *
- * @hide for internal use only
- */
-public class InstallerConnection {
-    private static final String TAG = "InstallerConnection";
-    private static final boolean LOCAL_DEBUG = false;
-
-    private InputStream mIn;
-    private OutputStream mOut;
-    private LocalSocket mSocket;
-
-    private volatile Object mWarnIfHeld;
-
-    private final byte buf[] = new byte[1024];
-
-    public InstallerConnection() {
-    }
-
-    /**
-     * Yell loudly if someone tries making future calls while holding a lock on
-     * the given object.
-     */
-    public void setWarnIfHeld(Object warnIfHeld) {
-        Preconditions.checkState(mWarnIfHeld == null);
-        mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
-    }
-
-    public synchronized String transact(String cmd) {
-        if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
-            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
-                    + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
-        }
-
-        if (!connect()) {
-            Slog.e(TAG, "connection failed");
-            return "-1";
-        }
-
-        if (!writeCommand(cmd)) {
-            /*
-             * If installd died and restarted in the background (unlikely but
-             * possible) we'll fail on the next write (this one). Try to
-             * reconnect and write the command one more time before giving up.
-             */
-            Slog.e(TAG, "write command failed? reconnect!");
-            if (!connect() || !writeCommand(cmd)) {
-                return "-1";
-            }
-        }
-        if (LOCAL_DEBUG) {
-            Slog.i(TAG, "send: '" + cmd + "'");
-        }
-
-        final int replyLength = readReply();
-        if (replyLength > 0) {
-            String s = new String(buf, 0, replyLength);
-            if (LOCAL_DEBUG) {
-                Slog.i(TAG, "recv: '" + s + "'");
-            }
-            return s;
-        } else {
-            if (LOCAL_DEBUG) {
-                Slog.i(TAG, "fail");
-            }
-            return "-1";
-        }
-    }
-
-    public String[] execute(String cmd, Object... args) throws InstallerException {
-        final StringBuilder builder = new StringBuilder(cmd);
-        for (Object arg : args) {
-            String escaped;
-            if (arg == null) {
-                escaped = "";
-            } else {
-                escaped = String.valueOf(arg);
-            }
-            if (escaped.indexOf('\0') != -1 || escaped.indexOf(' ') != -1 || "!".equals(escaped)) {
-                throw new InstallerException(
-                        "Invalid argument while executing " + cmd + " " + Arrays.toString(args));
-            }
-            if (TextUtils.isEmpty(escaped)) {
-                escaped = "!";
-            }
-            builder.append(' ').append(escaped);
-        }
-        final String[] resRaw = transact(builder.toString()).split(" ");
-        int res = -1;
-        try {
-            res = Integer.parseInt(resRaw[0]);
-        } catch (ArrayIndexOutOfBoundsException | NumberFormatException ignored) {
-        }
-        if (res != 0) {
-            throw new InstallerException(
-                    "Failed to execute " + cmd + " " + Arrays.toString(args) + ": " + res);
-        }
-        return resRaw;
-    }
-
-    public void dexopt(String apkPath, int uid, String instructionSet, int dexoptNeeded,
-            int dexFlags, String compilerFilter, String volumeUuid, String sharedLibraries)
-            throws InstallerException {
-        dexopt(apkPath, uid, "*", instructionSet, dexoptNeeded, null /*outputPath*/, dexFlags,
-                compilerFilter, volumeUuid, sharedLibraries);
-    }
-
-    public void dexopt(String apkPath, int uid, String pkgName, String instructionSet,
-            int dexoptNeeded, String outputPath, int dexFlags, String compilerFilter,
-            String volumeUuid, String sharedLibraries) throws InstallerException {
-        execute("dexopt",
-                apkPath,
-                uid,
-                pkgName,
-                instructionSet,
-                dexoptNeeded,
-                outputPath,
-                dexFlags,
-                compilerFilter,
-                volumeUuid,
-                sharedLibraries);
-    }
-
-    private boolean safeParseBooleanResult(String[] res) throws InstallerException {
-        if ((res == null) || (res.length != 2)) {
-            throw new InstallerException("Invalid size result: " + Arrays.toString(res));
-        }
-
-        // Just as a sanity check. Anything != "true" will be interpreted as false by parseBoolean.
-        if (!res[1].equals("true") && !res[1].equals("false")) {
-            throw new InstallerException("Invalid boolean result: " + Arrays.toString(res));
-        }
-
-        return Boolean.parseBoolean(res[1]);
-    }
-
-    public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
-        final String[] res = execute("merge_profiles", uid, pkgName);
-
-        return safeParseBooleanResult(res);
-    }
-
-    public boolean dumpProfiles(String gid, String packageName, String codePaths)
-            throws InstallerException {
-        final String[] res = execute("dump_profiles", gid, packageName, codePaths);
-
-        return safeParseBooleanResult(res);
-    }
-
-    private boolean connect() {
-        if (mSocket != null) {
-            return true;
-        }
-        Slog.i(TAG, "connecting...");
-        try {
-            mSocket = new LocalSocket();
-
-            LocalSocketAddress address = new LocalSocketAddress("installd",
-                    LocalSocketAddress.Namespace.RESERVED);
-
-            mSocket.connect(address);
-
-            mIn = mSocket.getInputStream();
-            mOut = mSocket.getOutputStream();
-        } catch (IOException ex) {
-            disconnect();
-            return false;
-        }
-        return true;
-    }
-
-    public void disconnect() {
-        Slog.i(TAG, "disconnecting...");
-        IoUtils.closeQuietly(mSocket);
-        IoUtils.closeQuietly(mIn);
-        IoUtils.closeQuietly(mOut);
-
-        mSocket = null;
-        mIn = null;
-        mOut = null;
-    }
-
-
-    private boolean readFully(byte[] buffer, int len) {
-        try {
-            Streams.readFully(mIn, buffer, 0, len);
-        } catch (IOException ioe) {
-            Slog.e(TAG, "read exception");
-            disconnect();
-            return false;
-        }
-
-        if (LOCAL_DEBUG) {
-            Slog.i(TAG, "read " + len + " bytes");
-        }
-
-        return true;
-    }
-
-    private int readReply() {
-        if (!readFully(buf, 2)) {
-            return -1;
-        }
-
-        final int len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8);
-        if ((len < 1) || (len > buf.length)) {
-            Slog.e(TAG, "invalid reply length (" + len + ")");
-            disconnect();
-            return -1;
-        }
-
-        if (!readFully(buf, len)) {
-            return -1;
-        }
-
-        return len;
-    }
-
-    private boolean writeCommand(String cmdString) {
-        final byte[] cmd = cmdString.getBytes();
-        final int len = cmd.length;
-        if ((len < 1) || (len > buf.length)) {
-            return false;
-        }
-
-        buf[0] = (byte) (len & 0xff);
-        buf[1] = (byte) ((len >> 8) & 0xff);
-        try {
-            mOut.write(buf, 0, 2);
-            mOut.write(cmd, 0, len);
-        } catch (IOException ex) {
-            Slog.e(TAG, "write error");
-            disconnect();
-            return false;
-        }
-        return true;
-    }
-
-    public void waitForConnection() {
-        for (;;) {
-            try {
-                execute("ping");
-                return;
-            } catch (InstallerException ignored) {
-            }
-            Slog.w(TAG, "installd not ready");
-            SystemClock.sleep(1000);
-        }
-    }
-
-    public static class InstallerException extends Exception {
-        public InstallerException(String detailMessage) {
-            super(detailMessage);
-        }
-    }
-}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index e1118d8..ef5231c 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -26,11 +26,16 @@
 import android.icu.util.ULocale;
 import android.net.LocalServerSocket;
 import android.opengl.EGL14;
+import android.os.IInstalld;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.ZygoteProcess;
+import android.os.storage.StorageManager;
 import android.security.keystore.AndroidKeyStoreProvider;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -41,7 +46,6 @@
 import android.webkit.WebViewFactory;
 import android.widget.TextView;
 
-import com.android.internal.os.InstallerConnection.InstallerException;
 
 import dalvik.system.DexFile;
 import dalvik.system.PathClassLoader;
@@ -492,54 +496,55 @@
      */
     private static void performSystemServerDexOpt(String classPath) {
         final String[] classPathElements = classPath.split(":");
-        final InstallerConnection installer = new InstallerConnection();
-        installer.waitForConnection();
+        final IInstalld installd = IInstalld.Stub
+                .asInterface(ServiceManager.getService("installd"));
         final String instructionSet = VMRuntime.getRuntime().vmInstructionSet();
 
-        try {
-            String sharedLibraries = "";
-            for (String classPathElement : classPathElements) {
-                // System server is fully AOTed and never profiled
-                // for profile guided compilation.
-                // TODO: Make this configurable between INTERPRET_ONLY, SPEED, SPACE and EVERYTHING?
+        String sharedLibraries = "";
+        for (String classPathElement : classPathElements) {
+            // System server is fully AOTed and never profiled
+            // for profile guided compilation.
+            // TODO: Make this configurable between INTERPRET_ONLY, SPEED, SPACE and EVERYTHING?
 
-                int dexoptNeeded;
-                try {
-                    dexoptNeeded = DexFile.getDexOptNeeded(
-                        classPathElement, instructionSet, "speed",
-                        false /* newProfile */);
-                } catch (FileNotFoundException ignored) {
-                    // Do not add to the classpath.
-                    Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
-                    continue;
-                } catch (IOException e) {
-                    // Not fully clear what to do here as we don't know the cause of the
-                    // IO exception. Add to the classpath to be conservative, but don't
-                    // attempt to compile it.
-                    Log.w(TAG, "Error checking classpath element for system server: "
-                            + classPathElement, e);
-                    dexoptNeeded = DexFile.NO_DEXOPT_NEEDED;
-                }
-
-                if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
-                    try {
-                        installer.dexopt(classPathElement, Process.SYSTEM_UID, instructionSet,
-                                dexoptNeeded, 0 /*dexFlags*/, "speed", null /*volumeUuid*/,
-                                sharedLibraries);
-                    } catch (InstallerException e) {
-                        // Ignore (but log), we need this on the classpath for fallback mode.
-                        Log.w(TAG, "Failed compiling classpath element for system server: "
-                                + classPathElement, e);
-                    }
-                }
-
-                if (!sharedLibraries.isEmpty()) {
-                    sharedLibraries += ":";
-                }
-                sharedLibraries += classPathElement;
+            int dexoptNeeded;
+            try {
+                dexoptNeeded = DexFile.getDexOptNeeded(
+                    classPathElement, instructionSet, "speed",
+                    false /* newProfile */);
+            } catch (FileNotFoundException ignored) {
+                // Do not add to the classpath.
+                Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
+                continue;
+            } catch (IOException e) {
+                // Not fully clear what to do here as we don't know the cause of the
+                // IO exception. Add to the classpath to be conservative, but don't
+                // attempt to compile it.
+                Log.w(TAG, "Error checking classpath element for system server: "
+                        + classPathElement, e);
+                dexoptNeeded = DexFile.NO_DEXOPT_NEEDED;
             }
-        } finally {
-            installer.disconnect();
+
+            if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+                final String packageName = "*";
+                final String outputPath = null;
+                final int dexFlags = 0;
+                final String compilerFilter = "speed";
+                final String uuid = StorageManager.UUID_PRIVATE_INTERNAL;
+                try {
+                    installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName,
+                            instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter,
+                            uuid, sharedLibraries);
+                } catch (RemoteException | ServiceSpecificException e) {
+                    // Ignore (but log), we need this on the classpath for fallback mode.
+                    Log.w(TAG, "Failed compiling classpath element for system server: "
+                            + classPathElement, e);
+                }
+            }
+
+            if (!sharedLibraries.isEmpty()) {
+                sharedLibraries += ":";
+            }
+            sharedLibraries += classPathElement;
         }
     }
 
diff --git a/core/java/com/android/internal/util/TokenBucket.java b/core/java/com/android/internal/util/TokenBucket.java
new file mode 100644
index 0000000..effb82b
--- /dev/null
+++ b/core/java/com/android/internal/util/TokenBucket.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.os.SystemClock;
+
+import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
+
+/**
+ * A class useful for rate-limiting or throttling that stores and distributes tokens.
+ *
+ * A TokenBucket starts with a fixed capacity of tokens, an initial amount of tokens, and
+ * a fixed filling period (in milliseconds).
+ *
+ * For every filling period, the bucket gains one token, up to its maximum capacity from
+ * which point tokens simply overflow and are lost. Tokens can be obtained one by one or n by n.
+ *
+ * The available amount of tokens is computed lazily when the bucket state is inspected.
+ * Therefore it is purely synchronous and does not involve any asynchronous activity.
+ * It is not synchronized in any way and not a thread-safe object.
+ */
+public class TokenBucket {
+
+    private final int mFillDelta; // Time in ms it takes to generate one token.
+    private final int mCapacity;  // Maximum number of tokens that can be stored.
+    private long mLastFill;       // Last time in ms the bucket generated tokens.
+    private int mAvailable;       // Current number of available tokens.
+
+    /**
+     * Create a new TokenBucket.
+     * @param deltaMs the time in milliseconds it takes to generate a new token.
+     * Must be strictly positive.
+     * @param capacity the maximum token capacity. Must be strictly positive.
+     * @param tokens the starting amount of token. Must be positive or zero.
+     */
+    public TokenBucket(int deltaMs, int capacity, int tokens) {
+        mFillDelta = checkArgumentPositive(deltaMs, "deltaMs must be strictly positive");
+        mCapacity = checkArgumentPositive(capacity, "capacity must be strictly positive");
+        mAvailable = Math.min(checkArgumentNonnegative(tokens), mCapacity);
+        mLastFill = scaledTime();
+    }
+
+    /**
+     * Create a new TokenBucket that starts completely filled.
+     * @param deltaMs the time in milliseconds it takes to generate a new token.
+     * Must be strictly positive.
+     * @param capacity the maximum token capacity. Must be strictly positive.
+     */
+    public TokenBucket(int deltaMs, int capacity) {
+        this(deltaMs, capacity, capacity);
+    }
+
+    /** Reset this TokenBucket and set its number of available tokens. */
+    public void reset(int tokens) {
+        checkArgumentNonnegative(tokens);
+        mAvailable = Math.min(tokens, mCapacity);
+        mLastFill = scaledTime();
+    }
+
+    /** Returns this TokenBucket maximum token capacity. */
+    public int capacity() {
+        return mCapacity;
+    }
+
+    /** Returns this TokenBucket currently number of available tokens. */
+    public int available() {
+        fill();
+        return mAvailable;
+    }
+
+    /** Returns true if this TokenBucket as one or more tokens available. */
+    public boolean has() {
+        fill();
+        return mAvailable > 0;
+    }
+
+    /** Consumes a token from this TokenBucket and returns true if a token is available. */
+    public boolean get() {
+        return (get(1) == 1);
+    }
+
+    /**
+     * Try to consume many tokens from this TokenBucket.
+     * @param n the number of tokens to consume.
+     * @return the number of tokens that were actually consumed.
+     */
+    public int get(int n) {
+        fill();
+        if (n <= 0) {
+            return 0;
+        }
+        if (n > mAvailable) {
+            int got = mAvailable;
+            mAvailable = 0;
+            return got;
+        }
+        mAvailable -= n;
+        return n;
+    }
+
+    private void fill() {
+        final long now = scaledTime();
+        final int diff = (int) (now - mLastFill);
+        mAvailable = Math.min(mCapacity, mAvailable + diff);
+        mLastFill = now;
+    }
+
+    private long scaledTime() {
+        return SystemClock.elapsedRealtime() / mFillDelta;
+    }
+}
diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
index 9fa90ac..4a2b881 100644
--- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
@@ -43,6 +43,7 @@
     jfieldID uid;
     jfieldID set;
     jfieldID tag;
+    jfieldID metered;
     jfieldID roaming;
     jfieldID rxBytes;
     jfieldID rxPackets;
@@ -239,6 +240,9 @@
     ScopedIntArrayRW tag(env, get_int_array(env, stats,
             gNetworkStatsClassInfo.tag, size, grow));
     if (tag.get() == NULL) return -1;
+    ScopedIntArrayRW metered(env, get_int_array(env, stats,
+            gNetworkStatsClassInfo.metered, size, grow));
+    if (metered.get() == NULL) return -1;
     ScopedIntArrayRW roaming(env, get_int_array(env, stats,
             gNetworkStatsClassInfo.roaming, size, grow));
     if (roaming.get() == NULL) return -1;
@@ -265,7 +269,7 @@
         uid[i] = lines[i].uid;
         set[i] = lines[i].set;
         tag[i] = lines[i].tag;
-        // Roaming is populated in Java-land by inspecting the iface properties.
+        // Metered and Roaming are populated in Java-land by inspecting the iface properties.
         rxBytes[i] = lines[i].rxBytes;
         rxPackets[i] = lines[i].rxPackets;
         txBytes[i] = lines[i].txBytes;
@@ -279,6 +283,7 @@
         env->SetObjectField(stats, gNetworkStatsClassInfo.uid, uid.getJavaArray());
         env->SetObjectField(stats, gNetworkStatsClassInfo.set, set.getJavaArray());
         env->SetObjectField(stats, gNetworkStatsClassInfo.tag, tag.getJavaArray());
+        env->SetObjectField(stats, gNetworkStatsClassInfo.metered, metered.getJavaArray());
         env->SetObjectField(stats, gNetworkStatsClassInfo.roaming, roaming.getJavaArray());
         env->SetObjectField(stats, gNetworkStatsClassInfo.rxBytes, rxBytes.getJavaArray());
         env->SetObjectField(stats, gNetworkStatsClassInfo.rxPackets, rxPackets.getJavaArray());
@@ -311,6 +316,7 @@
     gNetworkStatsClassInfo.uid = GetFieldIDOrDie(env, clazz, "uid", "[I");
     gNetworkStatsClassInfo.set = GetFieldIDOrDie(env, clazz, "set", "[I");
     gNetworkStatsClassInfo.tag = GetFieldIDOrDie(env, clazz, "tag", "[I");
+    gNetworkStatsClassInfo.metered = GetFieldIDOrDie(env, clazz, "metered", "[I");
     gNetworkStatsClassInfo.roaming = GetFieldIDOrDie(env, clazz, "roaming", "[I");
     gNetworkStatsClassInfo.rxBytes = GetFieldIDOrDie(env, clazz, "rxBytes", "[J");
     gNetworkStatsClassInfo.rxPackets = GetFieldIDOrDie(env, clazz, "rxPackets", "[J");
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 22255b4..25021b8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1681,7 +1681,7 @@
     <bool name="config_actionMenuItemAllCaps">true</bool>
 
     <!-- Remote server that can provide NTP responses. -->
-    <string translatable="false" name="config_ntpServer">2.android.pool.ntp.org</string>
+    <string translatable="false" name="config_ntpServer">time.android.com</string>
     <!-- Normal polling frequency in milliseconds -->
     <integer name="config_ntpPollingInterval">86400000</integer>
     <!-- Try-again polling interval in milliseconds, in case the network request failed -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c26fac1..ddf8f25 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2716,7 +2716,6 @@
 
   <java-symbol type="bool" name="config_permissionReviewRequired" />
 
-
   <java-symbol type="drawable" name="ic_restart" />
 
   <java-symbol type="drawable" name="emergency_icon" />
@@ -2731,5 +2730,4 @@
 
 <!-- Network Recommendation -->
   <java-symbol type="array" name="config_networkRecommendationPackageNames" />
-
 </resources>
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 9074f8a..eb85eb4 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -16,6 +16,9 @@
 
 package android.net;
 
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
 import static android.net.NetworkStats.ROAMING_ALL;
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.ROAMING_YES;
@@ -45,103 +48,124 @@
     private static final long TEST_START = 1194220800000L;
 
     public void testFindIndex() throws Exception {
-        final NetworkStats stats = new NetworkStats(TEST_START, 4)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L, 0L,
-                        0L, 10)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 1024L,
-                        8L, 11)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                        1024L, 8L, 12)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_YES, 1024L, 8L,
-                        1024L, 8L, 12);
+        final NetworkStats stats = new NetworkStats(TEST_START, 5)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
+                        8L, 0L, 0L, 10)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L, 0L,
+                        1024L, 8L, 11)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 0L, 0L,
+                        1024L, 8L, 11)
+                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
+                        8L, 1024L, 8L, 12)
+                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, 1024L,
+                        8L, 1024L, 8L, 12);
 
-        assertEquals(3, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_YES));
-        assertEquals(2, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_NO));
-        assertEquals(1, stats.findIndex(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO));
-        assertEquals(0, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO));
-        assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, ROAMING_NO));
+        assertEquals(4, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES,
+                ROAMING_YES));
+        assertEquals(3, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO));
+        assertEquals(2, stats.findIndex(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES,
+                ROAMING_NO));
+        assertEquals(1, stats.findIndex(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO));
+        assertEquals(0, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO));
+        assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO));
     }
 
     public void testFindIndexHinted() {
         final NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L, 0L,
-                        0L, 10)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 1024L,
-                        8L, 11)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                        1024L, 8L, 12)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                        0L, 0L, 10)
-                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, ROAMING_NO, 0L, 0L, 1024L,
-                        8L, 11)
-                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                        1024L, 8L, 12)
-                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, ROAMING_YES, 1024L, 8L,
-                        1024L, 8L, 12);
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
+                        8L, 0L, 0L, 10)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L, 0L,
+                        1024L, 8L, 11)
+                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
+                        8L, 1024L, 8L, 12)
+                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        1024L, 8L, 0L, 0L, 10)
+                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 0L, 0L,
+                        1024L, 8L, 11)
+                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, 0L, 0L,
+                        1024L, 8L, 11)
+                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
+                        8L, 1024L, 8L, 12)
+                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, 1024L,
+                        8L, 1024L, 8L, 12);
 
         // verify that we correctly find across regardless of hinting
         for (int hint = 0; hint < stats.size(); hint++) {
             assertEquals(0, stats.findIndexHinted(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
             assertEquals(1, stats.findIndexHinted(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
             assertEquals(2, stats.findIndexHinted(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
             assertEquals(3, stats.findIndexHinted(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
             assertEquals(4, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D,
-                    ROAMING_NO, hint));
-            assertEquals(5, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
+            assertEquals(5, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D,
+                    METERED_YES, ROAMING_NO, hint));
             assertEquals(6, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE,
-                    ROAMING_YES, hint));
+                    METERED_NO, ROAMING_NO, hint));
+            assertEquals(7, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE,
+                    METERED_YES, ROAMING_YES, hint));
             assertEquals(-1, stats.findIndexHinted(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
         }
     }
 
     public void testAddEntryGrow() throws Exception {
-        final NetworkStats stats = new NetworkStats(TEST_START, 3);
+        final NetworkStats stats = new NetworkStats(TEST_START, 4);
 
         assertEquals(0, stats.size());
-        assertEquals(3, stats.internalSize());
+        assertEquals(4, stats.internalSize());
 
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1L, 1L, 2L,
-                2L, 3);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 2L, 2L, 2L,
-                2L, 4);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_YES, 3L, 3L, 2L,
-                2L, 5);
-
-        assertEquals(3, stats.size());
-        assertEquals(3, stats.internalSize());
-
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 4L, 40L, 4L,
-                40L, 7);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 5L, 50L, 4L,
-                40L, 8);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 6L, 60L, 5L,
-                50L, 10);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_YES, 7L, 70L, 5L,
-                50L, 11);
-
-        assertEquals(7, stats.size());
-        assertTrue(stats.internalSize() >= 7);
-
-        assertValues(stats, 0, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1L, 1L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1L, 1L,
                 2L, 2L, 3);
-        assertValues(stats, 1, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 2L, 2L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 2L, 2L,
                 2L, 2L, 4);
-        assertValues(stats, 2, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_YES, 3L, 3L,
-                2L, 2L, 5);
-        assertValues(stats, 3, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 4L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, 3L,
+                3L, 2L, 2L, 5);
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, 3L,
+                3L, 2L, 2L, 5);
+
+        assertEquals(4, stats.size());
+        assertEquals(4, stats.internalSize());
+
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 4L,
                 40L, 4L, 40L, 7);
-        assertValues(stats, 4, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 5L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 5L,
                 50L, 4L, 40L, 8);
-        assertValues(stats, 5, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 6L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 6L,
                 60L, 5L, 50L, 10);
-        assertValues(stats, 6, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_YES, 7L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, 7L,
                 70L, 5L, 50L, 11);
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, 7L,
+                70L, 5L, 50L, 11);
+
+        assertEquals(9, stats.size());
+        assertTrue(stats.internalSize() >= 9);
+
+        assertValues(stats, 0, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                1L, 1L, 2L, 2L, 3);
+        assertValues(stats, 1, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                2L, 2L, 2L, 2L, 4);
+        assertValues(stats, 2, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+                3L, 3L, 2L, 2L, 5);
+        assertValues(stats, 3, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES,
+                ROAMING_YES, 3L, 3L, 2L, 2L, 5);
+        assertValues(stats, 4, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                4L, 40L, 4L, 40L, 7);
+        assertValues(stats, 5, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                5L, 50L, 4L, 40L, 8);
+        assertValues(stats, 6, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                6L, 60L, 5L, 50L, 10);
+        assertValues(stats, 7, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+                7L, 70L, 5L, 50L, 11);
+        assertValues(stats, 8, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES,
+                ROAMING_YES, 7L, 70L, 5L, 50L, 11);
     }
 
     public void testCombineExisting() throws Exception {
@@ -152,20 +176,18 @@
         stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L,
                 -128L, -1L, -1);
 
-        assertValues(stats, 0, TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, ROAMING_NO, 384L, 3L,
-                128L, 1L, 9);
-        assertValues(stats, 1, TEST_IFACE, 1001, SET_DEFAULT, 0xff, ROAMING_NO, 128L, 1L, 128L,
-                1L, 2);
+        assertValues(stats, 0, TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                384L, 3L, 128L, 1L, 9);
+        assertValues(stats, 1, TEST_IFACE, 1001, SET_DEFAULT, 0xff, METERED_NO, ROAMING_NO, 128L,
+                1L, 128L, 1L, 2);
 
         // now try combining that should create row
-        stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L,
-                128L, 1L, 3);
-        assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 1L,
-                128L, 1L, 3);
-        stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L,
-                128L, 1L, 3);
-        assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, ROAMING_NO, 256L, 2L,
-                256L, 2L, 6);
+        stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+        assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                128L, 1L, 128L, 1L, 3);
+        stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+        assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                256L, 2L, 256L, 2L, 6);
     }
 
     public void testSubtractIdenticalData() throws Exception {
@@ -180,10 +202,10 @@
         final NetworkStats result = after.subtract(before);
 
         // identical data should result in zero delta
-        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 0L,
-                0L, 0);
-        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 0L,
-                0L, 0);
+        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L,
+                0L, 0L, 0L, 0);
+        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L,
+                0L, 0L, 0L, 0);
     }
 
     public void testSubtractIdenticalRows() throws Exception {
@@ -198,10 +220,10 @@
         final NetworkStats result = after.subtract(before);
 
         // expect delta between measurements
-        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1L, 1L, 2L,
-                1L, 4);
-        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 3L, 1L, 4L,
-                1L, 8);
+        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1L,
+                1L, 2L, 1L, 4);
+        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 3L,
+                1L, 4L, 1L, 8);
     }
 
     public void testSubtractNewRows() throws Exception {
@@ -217,12 +239,12 @@
         final NetworkStats result = after.subtract(before);
 
         // its okay to have new rows
-        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 0L,
-                0L, 0);
-        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 0L,
-                0L, 0);
-        assertValues(result, 2, TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                1024L, 8L, 20);
+        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L,
+                0L, 0L, 0L, 0);
+        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L,
+                0L, 0L, 0L, 0);
+        assertValues(result, 2, TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                1024L, 8L, 1024L, 8L, 20);
     }
 
     public void testSubtractMissingRows() throws Exception {
@@ -237,8 +259,8 @@
 
         // should silently drop omitted rows
         assertEquals(1, result.size());
-        assertValues(result, 0, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1L,
-                2L, 3L, 4L, 0);
+        assertValues(result, 0, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 1L, 2L, 3L, 4L, 0);
         assertEquals(4L, result.getTotalBytes());
     }
 
@@ -263,13 +285,22 @@
                 .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
         assertEquals(64L, uidTag.getTotalBytes());
 
+        final NetworkStats uidMetered = new NetworkStats(TEST_START, 3)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L);
+        assertEquals(96L, uidMetered.getTotalBytes());
+
         final NetworkStats uidRoaming = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L, 0L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L, 0L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_YES, 32L, 0L, 0L, 0L,
-                        0L);
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, 32L, 0L,
+                        0L, 0L, 0L);
         assertEquals(96L, uidRoaming.getTotalBytes());
     }
 
@@ -283,95 +314,95 @@
 
     public void testGroupedByIfaceAll() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
-                .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, ROAMING_NO, 128L, 8L, 0L, 2L,
-                        20L)
-                .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 128L, 8L, 0L,
+                .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L, 0L,
                         2L, 20L)
-                .addValues(IFACE_ALL, 101, SET_ALL, TAG_NONE, ROAMING_YES, 128L, 8L, 0L, 2L,
-                        20L);
+                .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO, 128L,
+                        8L, 0L, 2L, 20L)
+                .addValues(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES, 128L, 8L, 0L,
+                        2L, 20L);
         final NetworkStats grouped = uidStats.groupedByIface();
 
         assertEquals(3, uidStats.size());
         assertEquals(1, grouped.size());
 
-        assertValues(grouped, 0, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, ROAMING_ALL, 384L, 24L, 0L,
-                6L, 0L);
+        assertValues(grouped, 0, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                384L, 24L, 0L, 6L, 0L);
     }
 
     public void testGroupedByIface() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 7)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 8L, 0L,
-                        2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 512L, 32L, 0L,
-                        0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, ROAMING_NO, 64L, 4L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 512L, 32L,
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L,
+                        0L, 2L, 20L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 512L,
+                        32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L, 4L,
                         0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 8L, 0L,
-                        0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, ROAMING_NO, 128L, 8L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_YES, 128L, 8L, 0L,
-                        0L, 0L);
+                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 512L,
+                        32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, 128L, 8L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, 128L,
+                        8L, 0L, 0L, 0L);
 
         final NetworkStats grouped = uidStats.groupedByIface();
 
         assertEquals(7, uidStats.size());
 
         assertEquals(2, grouped.size());
-        assertValues(grouped, 0, TEST_IFACE, UID_ALL, SET_ALL, TAG_NONE, ROAMING_ALL, 384L, 24L, 0L,
-                2L, 0L);
-        assertValues(grouped, 1, TEST_IFACE2, UID_ALL, SET_ALL, TAG_NONE, ROAMING_ALL, 1024L, 64L,
-                0L, 0L, 0L);
+        assertValues(grouped, 0, TEST_IFACE, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                384L, 24L, 0L, 2L, 0L);
+        assertValues(grouped, 1, TEST_IFACE2, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                1024L, 64L, 0L, 0L, 0L);
     }
 
     public void testAddAllValues() {
         final NetworkStats first = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L, 0L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 32L, 0L, 0L,
-                        0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_YES, 32L, 0L, 0L,
-                        0L, 0L);
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 32L,
+                        0L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, 32L,
+                        0L, 0L, 0L, 0L);
 
         final NetworkStats second = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L, 0L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L, 0L,
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
                         0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_YES, 32L, 0L, 0L,
-                        0L, 0L);
+                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 32L,
+                        0L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, 32L,
+                        0L, 0L, 0L, 0L);
 
         first.combineAllValues(second);
 
         assertEquals(4, first.size());
-        assertValues(first, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 64L, 0L, 0L,
-                0L, 0L);
-        assertValues(first, 1, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 32L, 0L,
-                0L, 0L, 0L);
-        assertValues(first, 2, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_YES, 64L, 0L,
-                0L, 0L, 0L);
-        assertValues(first, 3, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L,
+        assertValues(first, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 64L,
                 0L, 0L, 0L, 0L);
+        assertValues(first, 1, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                32L, 0L, 0L, 0L, 0L);
+        assertValues(first, 2, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+                64L, 0L, 0L, 0L, 0L);
+        assertValues(first, 3, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                32L, 0L, 0L, 0L, 0L);
     }
 
     public void testGetTotal() {
         final NetworkStats stats = new NetworkStats(TEST_START, 7)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 8L, 0L,
-                        2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 512L, 32L, 0L,
-                        0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, ROAMING_NO, 64L, 4L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 512L, 32L,
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L,
+                        0L, 2L, 20L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 512L,
+                        32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L, 4L,
                         0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 8L, 0L,
-                        0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, ROAMING_NO, 128L, 8L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_YES, 128L, 8L, 0L,
-                        0L, 0L);
+                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 512L,
+                        32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 128L,
+                        8L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 128L, 8L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, 128L,
+                        8L, 0L, 0L, 0L);
 
         assertValues(stats.getTotal(null), 1408L, 88L, 0L, 2L, 20L);
         assertValues(stats.getTotal(null, 100), 1280L, 80L, 0L, 2L, 20L);
@@ -396,10 +427,10 @@
         final NetworkStats after = before.withoutUids(new int[] { 100 });
         assertEquals(6, before.size());
         assertEquals(2, after.size());
-        assertValues(after, 0, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 8L,
-                0L, 0L, 0L);
-        assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, ROAMING_NO, 128L, 8L, 0L,
-                0L, 0L);
+        assertValues(after, 0, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                128L, 8L, 0L, 0L, 0L);
+        assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 128L,
+                8L, 0L, 0L, 0L);
     }
 
     public void testClone() throws Exception {
@@ -434,105 +465,175 @@
         final String underlyingIface = "wlan0";
         final int testTag1 = 8888;
         NetworkStats delta = new NetworkStats(TEST_START, 17)
-            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, 39605L, 46L, 12259L, 55L, 0L)
-            .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
-            .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, 72667L, 197L, 43909L, 241L, 0L)
-            .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, 9297L, 17L, 4128L, 21L, 0L)
+            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 39605L, 46L,
+                    12259L, 55L, 0L)
+            .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 0L, 0L,
+                    0L, 0L, 0L)
+            .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 72667L, 197L,
+                    43909L, 241L, 0L)
+            .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 9297L,
+                    17L, 4128L, 21L, 0L)
             // VPN package also uses some traffic through unprotected network.
-            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, 4983L, 10L, 1801L, 12L, 0L)
-            .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 4983L, 10L,
+                    1801L, 12L, 0L)
+            .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 0L, 0L,
+                    0L, 0L, 0L)
             // Tag entries
-            .addValues(tunIface, 10120, SET_DEFAULT, testTag1, 21691L, 41L, 13820L, 51L, 0L)
-            .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, 1281L, 2L, 665L, 2L, 0L)
+            .addValues(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO, 21691L, 41L,
+                    13820L, 51L, 0L)
+            .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO, 1281L, 2L,
+                    665L, 2L, 0L)
             // Irrelevant entries
-            .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, 1685L, 5L, 2070L, 6L, 0L)
+            .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1685L, 5L,
+                    2070L, 6L, 0L)
             // Underlying Iface entries
-            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, 5178L, 8L, 2139L, 11L, 0L)
-            .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
-            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, 149873L, 287L,
-                    59217L /* smaller than sum(tun0) */, 299L /* smaller than sum(tun0) */, 0L)
-            .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 5178L,
+                    8L, 2139L, 11L, 0L)
+            .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 0L,
+                    0L, 0L, 0L, 0L)
+            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                    149873L, 287L, 59217L /* smaller than sum(tun0) */,
+                    299L /* smaller than sum(tun0) */, 0L)
+            .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                    0L, 0L, 0L, 0L, 0L);
 
         assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface));
-        assertEquals(21, delta.size());
+        assertEquals(20, delta.size());
 
         // tunIface and TEST_IFACE entries are not changed.
-        assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 39605L, 46L, 12259L, 55L, 0L);
-        assertValues(delta, 1, tunIface, 10100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 0L, 0L,
-                0L, 0L, 0L);
-        assertValues(delta, 2, tunIface, 10120, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 1, tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                0L, 0L, 0L, 0L, 0L);
+        assertValues(delta, 2, tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 72667L, 197L, 43909L, 241L, 0L);
-        assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                 9297L, 17L, 4128L, 21L, 0L);
-        assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 4983L, 10L, 1801L, 12L, 0L);
-        assertValues(delta, 5, tunIface, tunUid, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 0L, 0L,
-                0L, 0L, 0L);
-        assertValues(delta, 6, tunIface, 10120, SET_DEFAULT, testTag1, ROAMING_NO,
+        assertValues(delta, 5, tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                0L, 0L, 0L, 0L, 0L);
+        assertValues(delta, 6, tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO,
                 21691L, 41L, 13820L, 51L, 0L);
-        assertValues(delta, 7, tunIface, 10120, SET_FOREGROUND, testTag1, ROAMING_NO, 1281L,
-                2L, 665L, 2L, 0L);
-        assertValues(delta, 8, TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1685L, 5L,
-                2070L, 6L, 0L);
+        assertValues(delta, 7, tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO,
+                1281L, 2L, 665L, 2L, 0L);
+        assertValues(delta, 8, TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                1685L, 5L, 2070L, 6L, 0L);
 
         // Existing underlying Iface entries are updated
-        assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                44783L, 54L, 13829L, 60L, 0L);
-        assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
-                0L, 0L, 0L, 0L, 0L);
+        assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 44783L, 54L, 14178L, 62L, 0L);
+        assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO,
+                ROAMING_NO, 0L, 0L, 0L, 0L, 0L);
 
         // VPN underlying Iface entries are updated
-        assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                28304L, 27L, 1719L, 12L, 0L);
-        assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
-                0L, 0L, 0L, 0L, 0L);
+        assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 28304L, 27L, 1L, 2L, 0L);
+        assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO,
+                ROAMING_NO, 0L, 0L, 0L, 0L, 0L);
 
         // New entries are added for new application's underlying Iface traffic
-        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                72667L, 197L, 41872L, 219L, 0L);
-        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
-                9297L, 17L, 3936, 19L, 0L);
-        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, testTag1, ROAMING_NO,
-                21691L, 41L, 13179L, 46L, 0L);
-        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, testTag1, ROAMING_NO,
-                1281L, 2L, 634L, 1L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 72667L, 197L, 43123L, 227L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO,
+                ROAMING_NO, 9297L, 17L, 4054, 19L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, testTag1, METERED_NO,
+                ROAMING_NO, 21691L, 41L, 13572L, 48L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, testTag1, METERED_NO,
+                ROAMING_NO, 1281L, 2L, 653L, 1L, 0L);
 
         // New entries are added for debug purpose
-        assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, ROAMING_NO,
-                39605L, 46L, 11690, 49, 0);
-        assertContains(delta, underlyingIface, 10120, SET_DBG_VPN_IN, TAG_NONE, ROAMING_NO,
-                81964, 214, 45808, 238, 0);
-        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_IN, TAG_NONE, ROAMING_NO,
-                4983, 10, 1717, 10, 0);
-        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, ROAMING_ALL,
-                126552, 270, 59215, 297, 0);
+        assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+                ROAMING_NO, 39605L, 46L, 12039, 51, 0);
+        assertContains(delta, underlyingIface, 10120, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+                ROAMING_NO, 81964, 214, 47177, 246, 0);
+        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, METERED_ALL,
+                ROAMING_ALL, 121569, 260, 59216, 297, 0);
 
     }
 
+    // Tests a case where all of the data received by the tun0 interface is echo back into the tun0
+    // interface by the vpn app before it's sent out of the underlying interface. The VPN app should
+    // not be charged for the echoed data but it should still be charged for any extra data it sends
+    // via the underlying interface.
+    public void testMigrateTun_VpnAsLoopback() {
+        final int tunUid = 10030;
+        final String tunIface = "tun0";
+        final String underlyingIface = "wlan0";
+        NetworkStats delta = new NetworkStats(TEST_START, 9)
+            // 2 different apps sent/receive data via tun0.
+            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 50000L, 25L,
+                    100000L, 50L, 0L)
+            .addValues(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 500L, 2L,
+                    200L, 5L, 0L)
+            // VPN package resends data through the tunnel (with exaggerated overhead)
+            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 240000,
+                    100L, 120000L, 60L, 0L)
+            // 1 app already has some traffic on the underlying interface, the other doesn't yet
+            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1000L,
+                    10L, 2000L, 20L, 0L)
+            // Traffic through the underlying interface via the vpn app.
+            // This test should redistribute this data correctly.
+            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                    75500L, 37L, 130000L, 70L, 0L);
+
+        assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface));
+        assertEquals(9, delta.size());
+
+        // tunIface entries should not be changed.
+        assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                50000L, 25L, 100000L, 50L, 0L);
+        assertValues(delta, 1, tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                500L, 2L, 200L, 5L, 0L);
+        assertValues(delta, 2, tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                240000L, 100L, 120000L, 60L, 0L);
+
+        // Existing underlying Iface entries are updated
+        assertValues(delta, 3, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 51000L, 35L, 102000L, 70L, 0L);
+
+        // VPN underlying Iface entries are updated
+        assertValues(delta, 4, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 25000L, 10L, 29800L, 15L, 0L);
+
+        // New entries are added for new application's underlying Iface traffic
+        assertContains(delta, underlyingIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 500L, 2L, 200L, 5L, 0L);
+
+        // New entries are added for debug purpose
+        assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+                ROAMING_NO, 50000L, 25L, 100000L, 50L, 0L);
+        assertContains(delta, underlyingIface, 20100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+                ROAMING_NO, 500, 2L, 200L, 5L, 0L);
+        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, METERED_ALL,
+                ROAMING_ALL, 50500L, 27L, 100200L, 55, 0);
+    }
+
     private static void assertContains(NetworkStats stats,  String iface, int uid, int set,
-            int tag, int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets,
-            long operations) {
-        int index = stats.findIndex(iface, uid, set, tag, roaming);
+            int tag, int metered, int roaming, long rxBytes, long rxPackets, long txBytes,
+            long txPackets, long operations) {
+        int index = stats.findIndex(iface, uid, set, tag, metered, roaming);
         assertTrue(index != -1);
-        assertValues(stats, index, iface, uid, set, tag, roaming,
+        assertValues(stats, index, iface, uid, set, tag, metered, roaming,
                 rxBytes, rxPackets, txBytes, txPackets, operations);
     }
 
     private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
-            int tag, int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets,
-            long operations) {
+            int tag, int metered, int roaming, long rxBytes, long rxPackets, long txBytes,
+            long txPackets, long operations) {
         final NetworkStats.Entry entry = stats.getValues(index, null);
-        assertValues(entry, iface, uid, set, tag, roaming);
+        assertValues(entry, iface, uid, set, tag, metered, roaming);
         assertValues(entry, rxBytes, rxPackets, txBytes, txPackets, operations);
     }
 
     private static void assertValues(
-            NetworkStats.Entry entry, String iface, int uid, int set, int tag, int roaming) {
+            NetworkStats.Entry entry, String iface, int uid, int set, int tag, int metered,
+            int roaming) {
         assertEquals(iface, entry.iface);
         assertEquals(uid, entry.uid);
         assertEquals(set, entry.set);
         assertEquals(tag, entry.tag);
+        assertEquals(metered, entry.metered);
         assertEquals(roaming, entry.roaming);
     }
 
diff --git a/core/tests/coretests/src/android/util/TokenBucketTest.java b/core/tests/coretests/src/android/util/TokenBucketTest.java
new file mode 100644
index 0000000..f7ac20c
--- /dev/null
+++ b/core/tests/coretests/src/android/util/TokenBucketTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import junit.framework.TestCase;
+
+public class TokenBucketTest extends TestCase {
+
+    static final int FILL_DELTA_VERY_SHORT  = 1;
+    static final int FILL_DELTA_VERY_LONG   = Integer.MAX_VALUE;
+
+    public void testArgumentValidation() {
+        assertThrow(() -> new TokenBucket(0, 1, 1));
+        assertThrow(() -> new TokenBucket(1, 0, 1));
+        assertThrow(() -> new TokenBucket(1, 1, 0));
+        assertThrow(() -> new TokenBucket(0, 1));
+        assertThrow(() -> new TokenBucket(1, 0));
+        assertThrow(() -> new TokenBucket(-1, 1, 1));
+        assertThrow(() -> new TokenBucket(1, -1, 1));
+        assertThrow(() -> new TokenBucket(1, 1, -1));
+        assertThrow(() -> new TokenBucket(-1, 1));
+        assertThrow(() -> new TokenBucket(1, -1));
+
+        new TokenBucket(1000, 100, 0);
+        new TokenBucket(1000, 100, 10);
+        new TokenBucket(5000, 50);
+        new TokenBucket(5000, 1);
+    }
+
+    public void testInitialCapacity() {
+        drain(new TokenBucket(FILL_DELTA_VERY_LONG, 1), 1);
+        drain(new TokenBucket(FILL_DELTA_VERY_LONG, 10), 10);
+        drain(new TokenBucket(FILL_DELTA_VERY_LONG, 1000), 1000);
+
+        drain(new TokenBucket(FILL_DELTA_VERY_LONG, 10, 0), 0);
+        drain(new TokenBucket(FILL_DELTA_VERY_LONG, 10, 3), 3);
+        drain(new TokenBucket(FILL_DELTA_VERY_LONG, 10, 10), 10);
+
+        drain(new TokenBucket(FILL_DELTA_VERY_LONG, 10, 100), 10);
+
+        drain(new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50), 50);
+        drain(new TokenBucket((int)DateUtils.HOUR_IN_MILLIS, 10), 10);
+        drain(new TokenBucket((int)DateUtils.DAY_IN_MILLIS, 200), 200);
+    }
+
+    public void testReset() {
+        TokenBucket tb = new TokenBucket(FILL_DELTA_VERY_LONG, 100, 10);
+        drain(tb, 10);
+
+        tb.reset(50);
+        drain(tb, 50);
+
+        tb.reset(50);
+        getOneByOne(tb, 10);
+        assertTrue(tb.has());
+
+        tb.reset(30);
+        drain(tb, 30);
+    }
+
+    public void testFill() throws Exception {
+        int delta = 50;
+        TokenBucket tb = new TokenBucket(delta, 10, 0);
+
+        assertEmpty(tb);
+
+        Thread.sleep(3 * delta / 2);
+
+        assertTrue(tb.has());
+    }
+
+    public void testRefill() throws Exception {
+        TokenBucket tb = new TokenBucket(FILL_DELTA_VERY_SHORT, 10, 10);
+
+        assertEquals(5, tb.get(5));
+        assertEquals(5, tb.get(5));
+
+        while (tb.available() < 10) {
+            Thread.sleep(2);
+        }
+
+        assertEquals(10, tb.get(10));
+
+        while (tb.available() < 10) {
+            Thread.sleep(2);
+        }
+
+        assertEquals(10, tb.get(100));
+    }
+
+    public void testAverage() throws Exception {
+        final int delta = 3;
+        final int want = 60;
+
+        long start = SystemClock.elapsedRealtime();
+        TokenBucket tb = new TokenBucket(delta, 20, 0);
+
+        for (int i = 0; i < want; i++) {
+            while (!tb.has()) {
+                Thread.sleep(5 * delta);
+            }
+            tb.get();
+        }
+
+        assertDuration(want * delta, SystemClock.elapsedRealtime() - start);
+    }
+
+    public void testBurst() throws Exception {
+        final int delta = 2;
+        final int capacity = 20;
+        final int want = 100;
+
+        long start = SystemClock.elapsedRealtime();
+        TokenBucket tb = new TokenBucket(delta, capacity, 0);
+
+        int total = 0;
+        while (total < want) {
+            while (!tb.has()) {
+                Thread.sleep(capacity * delta - 2);
+            }
+            total += tb.get(tb.available());
+        }
+
+        assertDuration(total * delta, SystemClock.elapsedRealtime() - start);
+    }
+
+    static void getOneByOne(TokenBucket tb, int n) {
+        while (n > 0) {
+            assertTrue(tb.has());
+            assertTrue(tb.available() >= n);
+            assertTrue(tb.get());
+            assertTrue(tb.available() >= n - 1);
+            n--;
+        }
+    }
+
+    void assertEmpty(TokenBucket tb) {
+        assertFalse(tb.has());
+        assertEquals(0, tb.available());
+        assertFalse(tb.get());
+    }
+
+    void drain(TokenBucket tb, int n) {
+        getOneByOne(tb, n);
+        assertEmpty(tb);
+    }
+
+    void assertDuration(long expected, long elapsed) {
+        String msg = String.format(
+            "expected elapsed time at least %d ms, but was %d ms", expected, elapsed);
+        elapsed += 1; // one millisecond extra guard
+        assertTrue(msg, elapsed >= expected);
+    }
+
+    void assertThrow(Fn fn)     {
+      try {
+          fn.call();
+          fail("expected n exception to be thrown.");
+      } catch (Throwable t) {}
+    }
+
+    interface Fn { void call(); }
+}
diff --git a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
index 327f3fd..7f13abc 100644
--- a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.net;
 
+import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
@@ -101,12 +102,14 @@
 
         final NetworkStats stats = mFactory.readNetworkStatsDetail();
         assertEquals(70, stats.size());
-        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L, 676L);
+        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L,
+                676L);
         assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
     }
 
     public void testNetworkStatsSingle() throws Exception {
-        stageFile(R.raw.xt_qtaguid_iface_typical, new File(mTestProc, "net/xt_qtaguid/iface_stat_all"));
+        stageFile(R.raw.xt_qtaguid_iface_typical,
+                new File(mTestProc, "net/xt_qtaguid/iface_stat_all"));
 
         final NetworkStats stats = mFactory.readNetworkStatsSummaryDev();
         assertEquals(6, stats.size());
@@ -122,7 +125,8 @@
         final NetworkStats stats = mFactory.readNetworkStatsSummaryXt();
         assertEquals(3, stats.size());
         assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 6824L, 16L, 5692L, 10L);
-        assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L, 2468L);
+        assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L,
+                2468L);
         assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L);
     }
 
@@ -157,7 +161,7 @@
 
     private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
             int tag, long rxBytes, long txBytes) {
-        final int i = stats.findIndex(iface, uid, set, tag, ROAMING_NO);
+        final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO);
         final NetworkStats.Entry entry = stats.getValues(i, null);
         assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
         assertEquals("unexpected txBytes", txBytes, entry.txBytes);
@@ -165,7 +169,7 @@
 
     private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
             int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
-        final int i = stats.findIndex(iface, uid, set, tag, ROAMING_NO);
+        final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO);
         final NetworkStats.Entry entry = stats.getValues(i, null);
         assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
         assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index 0011002..7bab189 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -21,10 +21,11 @@
 # This contains the junit.framework classes that were in Android API level 25.
 include $(CLEAR_VARS)
 
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_STATIC_JAVA_LIBRARIES := core-junit-static
-
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_MODULE := legacy-test
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_STATIC_JAVA_LIBRARIES := core-junit-static
 
 include $(BUILD_JAVA_LIBRARY)
 
@@ -34,7 +35,7 @@
 # This contains the android.test.PerformanceTestCase class only
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := ../core/java/android/test/PerformanceTestCase.java
+LOCAL_SRC_FILES := src/android/test/PerformanceTestCase.java
 LOCAL_MODULE := legacy-performance-test-hostdex
 
 include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
diff --git a/core/java/android/test/AndroidTestCase.java b/legacy-test/src/android/test/AndroidTestCase.java
similarity index 100%
rename from core/java/android/test/AndroidTestCase.java
rename to legacy-test/src/android/test/AndroidTestCase.java
diff --git a/core/java/android/test/FlakyTest.java b/legacy-test/src/android/test/FlakyTest.java
similarity index 100%
rename from core/java/android/test/FlakyTest.java
rename to legacy-test/src/android/test/FlakyTest.java
diff --git a/core/java/android/test/InstrumentationTestCase.java b/legacy-test/src/android/test/InstrumentationTestCase.java
similarity index 100%
rename from core/java/android/test/InstrumentationTestCase.java
rename to legacy-test/src/android/test/InstrumentationTestCase.java
diff --git a/core/java/android/test/InstrumentationTestSuite.java b/legacy-test/src/android/test/InstrumentationTestSuite.java
similarity index 100%
rename from core/java/android/test/InstrumentationTestSuite.java
rename to legacy-test/src/android/test/InstrumentationTestSuite.java
diff --git a/core/java/android/test/PerformanceTestCase.java b/legacy-test/src/android/test/PerformanceTestCase.java
similarity index 100%
rename from core/java/android/test/PerformanceTestCase.java
rename to legacy-test/src/android/test/PerformanceTestCase.java
diff --git a/core/java/android/test/RepetitiveTest.java b/legacy-test/src/android/test/RepetitiveTest.java
similarity index 100%
rename from core/java/android/test/RepetitiveTest.java
rename to legacy-test/src/android/test/RepetitiveTest.java
diff --git a/core/java/android/test/UiThreadTest.java b/legacy-test/src/android/test/UiThreadTest.java
similarity index 100%
rename from core/java/android/test/UiThreadTest.java
rename to legacy-test/src/android/test/UiThreadTest.java
diff --git a/core/java/android/test/package.html b/legacy-test/src/android/test/package.html
similarity index 100%
rename from core/java/android/test/package.html
rename to legacy-test/src/android/test/package.html
diff --git a/core/java/android/test/suitebuilder/annotation/LargeTest.java b/legacy-test/src/android/test/suitebuilder/annotation/LargeTest.java
similarity index 100%
rename from core/java/android/test/suitebuilder/annotation/LargeTest.java
rename to legacy-test/src/android/test/suitebuilder/annotation/LargeTest.java
diff --git a/core/java/android/test/suitebuilder/annotation/MediumTest.java b/legacy-test/src/android/test/suitebuilder/annotation/MediumTest.java
similarity index 100%
rename from core/java/android/test/suitebuilder/annotation/MediumTest.java
rename to legacy-test/src/android/test/suitebuilder/annotation/MediumTest.java
diff --git a/core/java/android/test/suitebuilder/annotation/SmallTest.java b/legacy-test/src/android/test/suitebuilder/annotation/SmallTest.java
similarity index 100%
rename from core/java/android/test/suitebuilder/annotation/SmallTest.java
rename to legacy-test/src/android/test/suitebuilder/annotation/SmallTest.java
diff --git a/core/java/android/test/suitebuilder/annotation/Smoke.java b/legacy-test/src/android/test/suitebuilder/annotation/Smoke.java
similarity index 100%
rename from core/java/android/test/suitebuilder/annotation/Smoke.java
rename to legacy-test/src/android/test/suitebuilder/annotation/Smoke.java
diff --git a/core/java/android/test/suitebuilder/annotation/Suppress.java b/legacy-test/src/android/test/suitebuilder/annotation/Suppress.java
similarity index 100%
rename from core/java/android/test/suitebuilder/annotation/Suppress.java
rename to legacy-test/src/android/test/suitebuilder/annotation/Suppress.java
diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java
index 61fbfb9..3c2ea58 100644
--- a/media/java/android/mtp/MtpServer.java
+++ b/media/java/android/mtp/MtpServer.java
@@ -67,6 +67,11 @@
         native_remove_storage(storage.getStorageId());
     }
 
+    public static void configure(boolean usePtp) {
+        native_configure(usePtp);
+    }
+
+    public static native final void native_configure(boolean usePtp);
     private native final void native_setup(MtpDatabase database, boolean usePtp);
     private native final void native_run();
     private native final void native_cleanup();
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index d13187c..afd3082 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -56,17 +56,16 @@
     return (MtpServer*)env->GetLongField(thiz, field_MtpServer_nativeContext);
 }
 
+static void android_mtp_configure(JNIEnv *, jobject, jboolean usePtp) {
+    MtpServer::configure(usePtp);
+}
+
 static void
 android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jboolean usePtp)
 {
-    int fd = open("/dev/mtp_usb", O_RDWR);
-    if (fd >= 0) {
-        MtpServer* server = new MtpServer(fd, getMtpDatabase(env, javaDatabase),
-                usePtp, AID_MEDIA_RW, 0664, 0775);
-        env->SetLongField(thiz, field_MtpServer_nativeContext, (jlong)server);
-    } else {
-        ALOGE("could not open MTP driver, errno: %d", errno);
-    }
+    MtpServer* server = new MtpServer(getMtpDatabase(env, javaDatabase),
+            usePtp, AID_MEDIA_RW, 0664, 0775);
+    env->SetLongField(thiz, field_MtpServer_nativeContext, (jlong)server);
 }
 
 static void
@@ -180,6 +179,7 @@
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] = {
+    {"native_configure",              "(Z)V",  (void *)android_mtp_configure},
     {"native_setup",                "(Landroid/mtp/MtpDatabase;Z)V",
                                             (void *)android_mtp_MtpServer_setup},
     {"native_run",                  "()V",  (void *)android_mtp_MtpServer_run},
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index b58c87a..bb8eb2c 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -115,6 +115,7 @@
         myWebView.clearCache(true);
         WebSettings webSettings = myWebView.getSettings();
         webSettings.setJavaScriptEnabled(true);
+        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
         mWebViewClient = new MyWebViewClient();
         myWebView.setWebViewClient(mWebViewClient);
         myWebView.setWebChromeClient(new MyWebChromeClient());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 70642ed..f0bbac8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -392,7 +392,7 @@
         RecentsActivityLaunchState launchState = config.getLaunchState();
         if (!loadPlan.hasTasks()) {
             loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
-                    !launchState.launchedFromHome);
+                    !launchState.launchedFromHome && !launchState.launchedViaDockGesture);
         }
 
         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
@@ -519,12 +519,14 @@
         EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
         MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
 
-        // Workaround for b/22542869, if the RecentsActivity is started again, but without going
-        // through SystemUI, we need to reset the config launch flags to ensure that we do not
-        // wait on the system to send a signal that was never queued.
-        RecentsConfiguration config = Recents.getConfiguration();
-        RecentsActivityLaunchState launchState = config.getLaunchState();
-        launchState.reset();
+        if (!isChangingConfigurations()) {
+            // Workaround for b/22542869, if the RecentsActivity is started again, but without going
+            // through SystemUI, we need to reset the config launch flags to ensure that we do not
+            // wait on the system to send a signal that was never queued.
+            RecentsConfiguration config = Recents.getConfiguration();
+            RecentsActivityLaunchState launchState = config.getLaunchState();
+            launchState.reset();
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 75b2dd4..5cf9eea 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -35,11 +35,13 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -154,6 +156,8 @@
     private final Map <Integer, ProfileServiceConnections> mProfileServices =
             new HashMap <Integer, ProfileServiceConnections>();
 
+    private final boolean mPermissionReviewRequired;
+
     private void registerForAirplaneMode(IntentFilter filter) {
         final ContentResolver resolver = mContext.getContentResolver();
         final String airplaneModeRadios = Settings.Global.getString(resolver,
@@ -183,7 +187,12 @@
             final boolean bluetoothDisallowed =
                     newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH);
             if ((mEnable || mEnableExternal) && bluetoothDisallowed) {
-                disable(true);
+                try {
+                  disable("android.os.UserManagerInternal", true);
+                } catch (RemoteException e) {
+                  // Shouldn't happen: startConsentUiIfNeeded not called
+                  // when from system.
+                }
             }
         }
     };
@@ -257,6 +266,11 @@
         mHandler = new BluetoothHandler(IoThread.get().getLooper());
 
         mContext = context;
+
+        mPermissionReviewRequired = Build.PERMISSIONS_REVIEW_REQUIRED
+                    || context.getResources().getBoolean(
+                com.android.internal.R.bool.config_permissionReviewRequired);
+
         mBluetooth = null;
         mBluetoothBinder = null;
         mBluetoothGatt = null;
@@ -665,7 +679,7 @@
         return true;
     }
 
-    public boolean enable() {
+    public boolean enable(String packageName) throws RemoteException {
         if (isBluetoothDisallowed()) {
             if (DBG) {
                 Slog.d(TAG,"enable(): not enabling - bluetooth disallowed");
@@ -673,14 +687,25 @@
             return false;
         }
 
-        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
-            (!checkIfCallerIsForegroundUser())) {
-            Slog.w(TAG,"enable(): not allowed for non-active and non system user");
-            return false;
+        final int callingUid = Binder.getCallingUid();
+        final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;
+
+        if (!callerSystem) {
+            if (!checkIfCallerIsForegroundUser()) {
+                Slog.w(TAG, "enable(): not allowed for non-active and non system user");
+                return false;
+            }
+
+            mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                    "Need BLUETOOTH ADMIN permission");
+
+            if (!isEnabled() && mPermissionReviewRequired
+                    && startConsentUiIfNeeded(packageName, callingUid,
+                            BluetoothAdapter.ACTION_REQUEST_ENABLE)) {
+                return false;
+            }
         }
 
-        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                                "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Slog.d(TAG,"enable():  mBluetooth =" + mBluetooth +
                     " mBinding = " + mBinding + " mState = " + mState);
@@ -696,14 +721,24 @@
         return true;
     }
 
-    public boolean disable(boolean persist) {
-        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                                "Need BLUETOOTH ADMIN permissicacheNameAndAddresson");
+    public boolean disable(String packageName, boolean persist) throws RemoteException {
+        final int callingUid = Binder.getCallingUid();
+        final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;
 
-        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
-            (!checkIfCallerIsForegroundUser())) {
-            Slog.w(TAG,"disable(): not allowed for non-active and non system user");
-            return false;
+        if (!callerSystem) {
+            if (!checkIfCallerIsForegroundUser()) {
+                Slog.w(TAG, "disable(): not allowed for non-active and non system user");
+                return false;
+            }
+
+            mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                    "Need BLUETOOTH ADMIN permission");
+
+            if (isEnabled() && mPermissionReviewRequired
+                    && startConsentUiIfNeeded(packageName, callingUid,
+                            BluetoothAdapter.ACTION_REQUEST_DISABLE)) {
+                return false;
+            }
         }
 
         if (DBG) {
@@ -724,6 +759,31 @@
         return true;
     }
 
+    private boolean startConsentUiIfNeeded(String packageName,
+            int callingUid, String intentAction) throws RemoteException {
+        try {
+            // Validate the package only if we are going to use it
+            ApplicationInfo applicationInfo = mContext.getPackageManager()
+                    .getApplicationInfoAsUser(packageName,
+                            PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+                            UserHandle.getUserId(callingUid));
+            if (applicationInfo.uid != callingUid) {
+                throw new SecurityException("Package " + callingUid
+                        + " not in uid " + callingUid);
+            }
+
+            // Legacy apps in permission review mode trigger a user prompt
+            if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                Intent intent = new Intent(intentAction);
+                mContext.startActivity(intent);
+                return true;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RemoteException(e.getMessage());
+        }
+        return false;
+    }
+
     public void unbindAndFinish() {
         if (DBG) {
             Slog.d(TAG,"unbindAndFinish(): " + mBluetooth +
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 090c744..8108c4c 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4615,28 +4615,9 @@
         } catch (Exception e) {
             loge("Exception in setDnsConfigurationForNetwork: " + e);
         }
-        final NetworkAgentInfo defaultNai = getDefaultNetwork();
-        if (defaultNai != null && defaultNai.network.netId == netId) {
-            setDefaultDnsSystemProperties(dnses);
-        }
         flushVmDnsCache();
     }
 
-    private void setDefaultDnsSystemProperties(Collection<InetAddress> dnses) {
-        int last = 0;
-        for (InetAddress dns : dnses) {
-            ++last;
-            String key = "net.dns" + last;
-            String value = dns.getHostAddress();
-            SystemProperties.set(key, value);
-        }
-        for (int i = last + 1; i <= mNumDnsEntries; ++i) {
-            String key = "net.dns" + i;
-            SystemProperties.set(key, "");
-        }
-        mNumDnsEntries = last;
-    }
-
     private String getNetworkPermission(NetworkCapabilities nc) {
         // TODO: make these permission strings AIDL constants instead.
         if (!nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
@@ -4853,7 +4834,6 @@
         notifyLockdownVpn(newNetwork);
         handleApplyDefaultProxy(newNetwork.linkProperties.getHttpProxy());
         updateTcpBufferSizes(newNetwork);
-        setDefaultDnsSystemProperties(newNetwork.linkProperties.getDnsServers());
     }
 
     private void processListenRequests(NetworkAgentInfo nai, boolean capabilitiesChanged) {
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 4405c1b..97f913e 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -49,7 +49,6 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.IDeviceIdleController;
 import android.os.IMaintenanceActivityListener;
 import android.os.Looper;
@@ -1238,7 +1237,7 @@
         }
     }
 
-    public final class LocalService {
+    public class LocalService {
         public void addPowerSaveTempWhitelistAppDirect(int appId, long duration, boolean sync,
                 String reason) {
             addPowerSaveTempWhitelistAppDirectInternal(0, appId, duration, sync, reason);
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 6412e01..27e4aa4 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -425,7 +425,9 @@
     }
 
     @Override
-    public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+    public void registerNetworkScoreCache(int networkType,
+                                          INetworkScoreCache scoreCache,
+                                          int filterType) {
         mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
         synchronized (mScoreCaches) {
             RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
@@ -433,7 +435,7 @@
                 callbackList = new RemoteCallbackList<>();
                 mScoreCaches.put(networkType, callbackList);
             }
-            if (!callbackList.register(scoreCache)) {
+            if (!callbackList.register(scoreCache, filterType)) {
                 if (callbackList.getRegisteredCallbackCount() == 0) {
                     mScoreCaches.remove(networkType);
                 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 10a5388..ae1aef6 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -369,7 +369,7 @@
         // we do not start the service and launch a review activity if the calling app
         // is in the foreground passing it a pending intent to start the service when
         // review is completed.
-        if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+        if (mAm.mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
             if (!requestStartTargetPermissionsReviewIfNeededLocked(r, callingPackage,
                     callingUid, service, callerFg, userId)) {
                 return null;
@@ -913,7 +913,7 @@
         // we schedule binding to the service but do not start its process, then
         // we launch a review activity to which is passed a callback to invoke
         // when done to start the bound service's process to completing the binding.
-        if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+        if (mAm.mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
             if (mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired(
                     s.packageName, s.userId)) {
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4cf1ef5..fa54a61 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -35,7 +35,6 @@
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.os.Zygote;
-import com.android.internal.os.InstallerConnection.InstallerException;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FastXmlSerializer;
@@ -54,6 +53,7 @@
 import com.android.server.am.ActivityStack.ActivityState;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.WindowManagerService;
@@ -1575,6 +1575,8 @@
     // being called for multiwindow assist in a single session.
     private int mViSessionId = 1000;
 
+    final boolean mPermissionReviewRequired;
+
     final class KillHandler extends Handler {
         static final int KILL_PROCESS_GROUP_MSG = 4000;
 
@@ -2622,6 +2624,9 @@
 
         Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass());
 
+        mPermissionReviewRequired = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_permissionReviewRequired);
+
         mHandlerThread = new ServiceThread(TAG,
                 android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
         mHandlerThread.start();
@@ -10824,7 +10829,7 @@
                     // If permissions need a review before any of the app components can run,
                     // we return no provider and launch a review activity if the calling app
                     // is in the foreground.
-                    if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+                    if (mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
                         if (!requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) {
                             return null;
                         }
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 115971f..907394e 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -416,7 +416,8 @@
         // If permissions need a review before any of the app components can run, we
         // launch the review activity and pass a pending intent to start the activity
         // we are to launching now after the review is completed.
-        if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) {
+        if ((mService.mPermissionReviewRequired
+                || Build.PERMISSIONS_REVIEW_REQUIRED) && aInfo != null) {
             if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
                     aInfo.packageName, userId)) {
                 IIntentSender target = mService.getIntentSenderLocked(
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 362a347..ea901ce 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -626,7 +626,7 @@
         // the broadcast and if the calling app is in the foreground and the broadcast is
         // explicit we launch the review UI passing it a pending intent to send the skipped
         // broadcast.
-        if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+        if (mService.mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
             if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
                     filter.owningUserId)) {
                 r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
@@ -1132,7 +1132,8 @@
             // the broadcast and if the calling app is in the foreground and the broadcast is
             // explicit we launch the review UI passing it a pending intent to send the skipped
             // broadcast.
-            if (Build.PERMISSIONS_REVIEW_REQUIRED && !skip) {
+            if ((mService.mPermissionReviewRequired
+                    || Build.PERMISSIONS_REVIEW_REQUIRED) && !skip) {
                 if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
                         info.activityInfo.packageName, UserHandle.getUserId(
                                 info.activityInfo.applicationInfo.uid))) {
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index f1ef947..f1d01e0 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -48,6 +48,10 @@
         final IpConnectivityLog log = new IpConnectivityLog();
         log.events = toProto(events);
         log.droppedEvents = dropped;
+        if ((log.events.length > 0) || (dropped > 0)) {
+            // Only write version number if log has some information at all.
+            log.version = IpConnectivityMetrics.VERSION;
+        }
         return IpConnectivityLog.toByteArray(log);
     }
 
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 28e724c..be68173 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -19,19 +19,25 @@
 import android.content.Context;
 import android.net.ConnectivityMetricsEvent;
 import android.net.IIpConnectivityMetrics;
+import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.os.IBinder;
 import android.os.Parcelable;
+import android.provider.Settings;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.util.Base64;
 import android.util.Log;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.TokenBucket;
 import com.android.server.SystemService;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.function.ToIntFunction;
 
 import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
 
@@ -40,10 +46,21 @@
     private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
     private static final boolean DBG = false;
 
+    // The logical version numbers of ipconnectivity.proto, corresponding to the
+    // "version" field of IpConnectivityLog.
+    private static final int NYC      = 0;
+    private static final int NYC_MR1  = 1;
+    private static final int NYC_MR2  = 2;
+    public static final int VERSION   = NYC_MR2;
+
     private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
 
     // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
     private static final int DEFAULT_BUFFER_SIZE = 2000;
+    // Maximum size of the event buffer.
+    private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
+
+    private static final int ERROR_RATE_LIMITED = -1;
 
     // Lock ensuring that concurrent manipulations of the event buffer are correct.
     // There are three concurrent operations to synchronize:
@@ -62,10 +79,19 @@
     private int mDropped;
     @GuardedBy("mLock")
     private int mCapacity;
+    @GuardedBy("mLock")
+    private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
+
+    private final ToIntFunction<Context> mCapacityGetter;
+
+    public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
+        super(ctx);
+        mCapacityGetter = capacityGetter;
+        initBuffer();
+    }
 
     public IpConnectivityMetrics(Context ctx) {
-        super(ctx);
-        initBuffer();
+        this(ctx, READ_BUFFER_SIZE);
     }
 
     @Override
@@ -86,7 +112,7 @@
 
     @VisibleForTesting
     public int bufferCapacity() {
-        return DEFAULT_BUFFER_SIZE; // TODO: read from config
+        return mCapacityGetter.applyAsInt(getContext());
     }
 
     private void initBuffer() {
@@ -104,6 +130,10 @@
             if (event == null) {
                 return left;
             }
+            if (isRateLimited(event)) {
+                // Do not count as a dropped event. TODO: consider adding separate counter
+                return ERROR_RATE_LIMITED;
+            }
             if (left == 0) {
                 mDropped++;
                 return 0;
@@ -113,6 +143,11 @@
         }
     }
 
+    private boolean isRateLimited(ConnectivityMetricsEvent event) {
+        TokenBucket tb = mBuckets.get(event.data.getClass());
+        return (tb != null) && !tb.get();
+    }
+
     private String flushEncodedOutput() {
         final ArrayList<ConnectivityMetricsEvent> events;
         final int dropped;
@@ -186,6 +221,7 @@
         static final String CMD_FLUSH   = "flush";
         static final String CMD_LIST    = "list";
         static final String CMD_STATS   = "stats";
+        static final String CMD_DUMPSYS = "-a"; // dumpsys.cpp dumps services with "-a" as arguments
         static final String CMD_DEFAULT = CMD_STATS;
 
         @Override
@@ -203,6 +239,8 @@
                 case CMD_FLUSH:
                     cmdFlush(fd, pw, args);
                     return;
+                case CMD_DUMPSYS:
+                    // Fallthrough to CMD_LIST when dumpsys.cpp dumps services states (bug reports)
                 case CMD_LIST:
                     cmdList(fd, pw, args);
                     return;
@@ -226,4 +264,20 @@
             getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
         }
     };
+
+    private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> {
+        int size = Settings.Global.getInt(ctx.getContentResolver(),
+                Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
+        if (size <= 0) {
+            return DEFAULT_BUFFER_SIZE;
+        }
+        return Math.min(size, MAXIMUM_BUFFER_SIZE);
+    };
+
+    private static ArrayMap<Class<?>, TokenBucket> makeRateLimitingBuckets() {
+        ArrayMap<Class<?>, TokenBucket> map = new ArrayMap<>();
+        // one token every minute, 50 tokens max: burst of ~50 events every hour.
+        map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
+        return map;
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 4b175d7..f8638c5 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -24,6 +24,7 @@
 import android.net.metrics.DnsEvent;
 import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
+import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -44,7 +45,7 @@
     public static final String SERVICE_NAME = "netd_listener";
 
     private static final String TAG = NetdEventListenerService.class.getSimpleName();
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
     private static final boolean VDBG = false;
 
     // TODO: read this constant from system property
@@ -86,7 +87,7 @@
             byte[] returnCodes = Arrays.copyOf(mReturnCodes, mEventCount);
             int[] latenciesMs = Arrays.copyOf(mLatenciesMs, mEventCount);
             mMetricsLog.log(new DnsEvent(mNetId, eventTypes, returnCodes, latenciesMs));
-            maybeLog(String.format("Logging %d results for netId %d", mEventCount, mNetId));
+            maybeLog("Logging %d results for netId %d", mEventCount, mNetId);
             mEventCount = 0;
         }
 
@@ -136,9 +137,9 @@
     // Called concurrently by multiple binder threads.
     // This method must not block or perform long-running operations.
     public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
-            String hostname, String[] ipAddresses, int ipAddressesCount, int uid) {
-        maybeVerboseLog(String.format("onDnsEvent(%d, %d, %d, %d)",
-                netId, eventType, returnCode, latencyMs));
+            String hostname, String[] ipAddresses, int ipAddressesCount, int uid)
+            throws RemoteException {
+        maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs);
 
         DnsEventBatch batch = mEventBatches.get(netId);
         if (batch == null) {
@@ -151,9 +152,9 @@
     @Override
     // Called concurrently by multiple binder threads.
     // This method must not block or perform long-running operations.
-    public synchronized void onConnectEvent(int netId, int latencyMs, String ipAddr, int port,
-            int uid) {
-        maybeVerboseLog(String.format("onConnectEvent(%d, %d)", netId, latencyMs));
+    public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port,
+            int uid) throws RemoteException {
+        maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs);
     }
 
     public synchronized void dump(PrintWriter writer) {
@@ -166,11 +167,11 @@
         pw.decreaseIndent();
     }
 
-    private static void maybeLog(String s) {
-        if (DBG) Log.d(TAG, s);
+    private static void maybeLog(String s, Object... args) {
+        if (DBG) Log.d(TAG, String.format(s, args));
     }
 
-    private static void maybeVerboseLog(String s) {
-        if (VDBG) Log.d(TAG, s);
+    private static void maybeVerboseLog(String s, Object... args) {
+        if (VDBG) Log.d(TAG, String.format(s, args));
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index c73d1dd..ea2cf5f 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -96,6 +96,24 @@
     private static final int SOCKET_TIMEOUT_MS = 10000;
     private static final int PROBE_TIMEOUT_MS  = 3000;
 
+    static enum EvaluationResult {
+        VALIDATED(true),
+        CAPTIVE_PORTAL(false);
+        final boolean isValidated;
+        EvaluationResult(boolean isValidated) {
+            this.isValidated = isValidated;
+        }
+    }
+
+    static enum ValidationStage {
+        FIRST_VALIDATION(true),
+        REVALIDATION(false);
+        final boolean isFirstValidation;
+        ValidationStage(boolean isFirstValidation) {
+            this.isFirstValidation = isFirstValidation;
+        }
+    }
+
     public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
             "android.net.conn.NETWORK_CONDITIONS_MEASURED";
     public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
@@ -215,6 +233,8 @@
     protected boolean mIsCaptivePortalCheckEnabled;
 
     private boolean mUseHttps;
+    // The total number of captive portal detection attempts for this NetworkMonitor instance.
+    private int mValidations = 0;
 
     // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
     private boolean mUserDoesNotWant = false;
@@ -289,6 +309,10 @@
         return validationLogs.readOnlyLocalLog();
     }
 
+    private ValidationStage validationStage() {
+        return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION;
+    }
+
     // DefaultState is the parent of all States.  It exists only to handle CMD_* messages but
     // does not entail any real state (hence no enter() or exit() routines).
     private class DefaultState extends State {
@@ -365,9 +389,11 @@
     private class ValidatedState extends State {
         @Override
         public void enter() {
-            maybeLogEvaluationResult(NetworkEvent.NETWORK_VALIDATED);
+            maybeLogEvaluationResult(
+                    networkEventType(validationStage(), EvaluationResult.VALIDATED));
             mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
                     NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null));
+            mValidations++;
         }
 
         @Override
@@ -583,7 +609,8 @@
 
         @Override
         public void enter() {
-            maybeLogEvaluationResult(NetworkEvent.NETWORK_CAPTIVE_PORTAL_FOUND);
+            maybeLogEvaluationResult(
+                    networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL));
             // Don't annoy user with sign-in notifications.
             if (mDontDisplaySigninNotification) return;
             // Create a CustomIntentReceiver that sends us a
@@ -603,6 +630,7 @@
             // Retest for captive portal occasionally.
             sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
                     CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
+            mValidations++;
         }
 
         @Override
@@ -678,48 +706,13 @@
 
         long startTime = SystemClock.elapsedRealtime();
 
-        // Pre-resolve the captive portal server host so we can log it.
-        // Only do this if HttpURLConnection is about to, to avoid any potentially
-        // unnecessary resolution.
-        String hostToResolve = null;
+        final CaptivePortalProbeResult result;
         if (pacUrl != null) {
-            hostToResolve = pacUrl.getHost();
-        } else if (proxyInfo != null) {
-            hostToResolve = proxyInfo.getHost();
-        } else {
-            hostToResolve = httpUrl.getHost();
-        }
-
-        if (!TextUtils.isEmpty(hostToResolve)) {
-            String probeName = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS);
-            final Stopwatch dnsTimer = new Stopwatch().start();
-            int dnsResult;
-            long dnsLatency;
-            try {
-                InetAddress[] addresses = mNetworkAgentInfo.network.getAllByName(hostToResolve);
-                dnsResult = ValidationProbeEvent.DNS_SUCCESS;
-                dnsLatency = dnsTimer.stop();
-                final StringBuffer connectInfo = new StringBuffer(", " + hostToResolve + "=");
-                for (InetAddress address : addresses) {
-                    connectInfo.append(address.getHostAddress());
-                    if (address != addresses[addresses.length-1]) connectInfo.append(",");
-                }
-                validationLog(probeName + " OK " + dnsLatency + "ms" + connectInfo);
-            } catch (UnknownHostException e) {
-                dnsResult = ValidationProbeEvent.DNS_FAILURE;
-                dnsLatency = dnsTimer.stop();
-                validationLog(probeName + " FAIL " + dnsLatency + "ms, " + hostToResolve);
-            }
-            logValidationProbe(dnsLatency, ValidationProbeEvent.PROBE_DNS, dnsResult);
-        }
-
-        CaptivePortalProbeResult result;
-        if (pacUrl != null) {
-            result = sendHttpProbe(pacUrl, ValidationProbeEvent.PROBE_PAC);
+            result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
         } else if (mUseHttps) {
-            result = sendParallelHttpProbes(httpsUrl, httpUrl, fallbackUrl);
+            result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl, fallbackUrl);
         } else {
-            result = sendHttpProbe(httpUrl, ValidationProbeEvent.PROBE_HTTP);
+            result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP);
         }
 
         long endTime = SystemClock.elapsedRealtime();
@@ -732,8 +725,50 @@
     }
 
     /**
-     * Do a URL fetch on a known server to see if we get the data we expect.
-     * Returns HTTP response code.
+     * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect.
+     * @return a CaptivePortalProbeResult inferred from the HTTP response.
+     */
+    private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) {
+        // Pre-resolve the captive portal server host so we can log it.
+        // Only do this if HttpURLConnection is about to, to avoid any potentially
+        // unnecessary resolution.
+        final String host = (proxy != null) ? proxy.getHost() : url.getHost();
+        sendDnsProbe(host);
+        return sendHttpProbe(url, probeType);
+    }
+
+    /** Do a DNS resolution of the given server. */
+    private void sendDnsProbe(String host) {
+        if (TextUtils.isEmpty(host)) {
+            return;
+        }
+
+        final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS);
+        final Stopwatch watch = new Stopwatch().start();
+        int result;
+        String connectInfo;
+        try {
+            InetAddress[] addresses = mNetworkAgentInfo.network.getAllByName(host);
+            result = ValidationProbeEvent.DNS_SUCCESS;
+            StringBuffer buffer = new StringBuffer(host).append("=");
+            for (InetAddress address : addresses) {
+                buffer.append(address.getHostAddress());
+                if (address != addresses[addresses.length-1]) buffer.append(",");
+            }
+            connectInfo = buffer.toString();
+        } catch (UnknownHostException e) {
+            result = ValidationProbeEvent.DNS_FAILURE;
+            connectInfo = host;
+        }
+        final long latency = watch.stop();
+        String resultString = (ValidationProbeEvent.DNS_SUCCESS == result) ? "OK" : "FAIL";
+        validationLog(String.format("%s %s %dms, %s", name, resultString, latency, connectInfo));
+        logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
+    }
+
+    /**
+     * Do a URL fetch on a known web server to see if we get the data we expect.
+     * @return a CaptivePortalProbeResult inferred from the HTTP response.
      */
     @VisibleForTesting
     protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType) {
@@ -800,7 +835,7 @@
     }
 
     private CaptivePortalProbeResult sendParallelHttpProbes(
-            URL httpsUrl, URL httpUrl, URL fallbackUrl) {
+            ProxyInfo proxy, URL httpsUrl, URL httpUrl, URL fallbackUrl) {
         // Number of probes to wait for. If a probe completes with a conclusive answer
         // it shortcuts the latch immediately by forcing the count to 0.
         final CountDownLatch latch = new CountDownLatch(2);
@@ -820,9 +855,10 @@
             @Override
             public void run() {
                 if (mIsHttps) {
-                    mResult = sendHttpProbe(httpsUrl, ValidationProbeEvent.PROBE_HTTPS);
+                    mResult =
+                            sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS);
                 } else {
-                    mResult = sendHttpProbe(httpUrl, ValidationProbeEvent.PROBE_HTTP);
+                    mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP);
                 }
                 if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) {
                     // Stop waiting immediately if https succeeds or if http finds a portal.
@@ -973,6 +1009,22 @@
         mMetricsLog.log(new NetworkEvent(mNetId, evtype));
     }
 
+    private int networkEventType(ValidationStage s, EvaluationResult r) {
+        if (s.isFirstValidation) {
+            if (r.isValidated) {
+                return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS;
+            } else {
+                return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND;
+            }
+        } else {
+            if (r.isValidated) {
+                return NetworkEvent.NETWORK_REVALIDATION_SUCCESS;
+            } else {
+                return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND;
+            }
+        }
+    }
+
     private void maybeLogEvaluationResult(int evtype) {
         if (mEvaluationTimer.isRunning()) {
             mMetricsLog.log(new NetworkEvent(mNetId, evtype, mEvaluationTimer.stop()));
@@ -981,6 +1033,8 @@
     }
 
     private void logValidationProbe(long durationMs, int probeType, int probeResult) {
+        probeType =
+                ValidationProbeEvent.makeProbeType(probeType, validationStage().isFirstValidation);
         mMetricsLog.log(new ValidationProbeEvent(mNetId, durationMs, probeType, probeResult));
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index ede3bda..afc6247 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -27,6 +27,8 @@
 import android.annotation.UserIdInt;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -76,6 +78,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.net.LegacyVpnInfo;
@@ -241,12 +244,14 @@
     /**
      * Update current state, dispaching event to listeners.
      */
-    private void updateState(DetailedState detailedState, String reason) {
+    @VisibleForTesting
+    protected void updateState(DetailedState detailedState, String reason) {
         if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
         mNetworkInfo.setDetailedState(detailedState, reason, null);
         if (mNetworkAgent != null) {
             mNetworkAgent.sendNetworkInfo(mNetworkInfo);
         }
+        updateAlwaysOnNotification(detailedState);
     }
 
     /**
@@ -280,7 +285,10 @@
         }
 
         mLockdown = (mAlwaysOn && lockdown);
-        if (!isCurrentPreparedPackage(packageName)) {
+        if (isCurrentPreparedPackage(packageName)) {
+            updateAlwaysOnNotification(mNetworkInfo.getDetailedState());
+        } else {
+            // Prepare this app. The notification will update as a side-effect of updateState().
             prepareInternal(packageName);
         }
         maybeRegisterPackageChangeReceiverLocked(packageName);
@@ -682,22 +690,19 @@
         }
     }
 
-    private void agentDisconnect(NetworkInfo networkInfo, NetworkAgent networkAgent) {
-        networkInfo.setIsAvailable(false);
-        networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
+    private void agentDisconnect(NetworkAgent networkAgent) {
         if (networkAgent != null) {
+            NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo);
+            networkInfo.setIsAvailable(false);
+            networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
             networkAgent.sendNetworkInfo(networkInfo);
         }
     }
 
-    private void agentDisconnect(NetworkAgent networkAgent) {
-        NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo);
-        agentDisconnect(networkInfo, networkAgent);
-    }
-
     private void agentDisconnect() {
         if (mNetworkInfo.isConnected()) {
-            agentDisconnect(mNetworkInfo, mNetworkAgent);
+            mNetworkInfo.setIsAvailable(false);
+            updateState(DetailedState.DISCONNECTED, "agentDisconnect");
             mNetworkAgent = null;
         }
     }
@@ -1250,6 +1255,43 @@
         }
     }
 
+    private void updateAlwaysOnNotification(DetailedState networkState) {
+        final boolean visible = (mAlwaysOn && networkState != DetailedState.CONNECTED);
+        updateAlwaysOnNotificationInternal(visible);
+    }
+
+    @VisibleForTesting
+    protected void updateAlwaysOnNotificationInternal(boolean visible) {
+        final UserHandle user = UserHandle.of(mUserHandle);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final NotificationManager notificationManager = NotificationManager.from(mContext);
+            if (!visible) {
+                notificationManager.cancelAsUser(TAG, 0, user);
+                return;
+            }
+            final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
+            final PendingIntent configIntent = PendingIntent.getActivityAsUser(
+                    mContext, /* request */ 0, intent,
+                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+                    null, user);
+            final Notification.Builder builder = new Notification.Builder(mContext)
+                    .setDefaults(0)
+                    .setSmallIcon(R.drawable.vpn_connected)
+                    .setContentTitle(mContext.getString(R.string.vpn_lockdown_disconnected))
+                    .setContentText(mContext.getString(R.string.vpn_lockdown_config))
+                    .setContentIntent(configIntent)
+                    .setCategory(Notification.CATEGORY_SYSTEM)
+                    .setPriority(Notification.PRIORITY_LOW)
+                    .setVisibility(Notification.VISIBILITY_PUBLIC)
+                    .setOngoing(true)
+                    .setColor(mContext.getColor(R.color.system_notification_accent_color));
+            notificationManager.notifyAsUser(TAG, 0, builder.build(), user);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     private native int jniCreate(int mtu);
     private native String jniGetName(int tun);
     private native int jniSetAddresses(String interfaze, String addresses);
diff --git a/services/core/java/com/android/server/net/NetworkIdentitySet.java b/services/core/java/com/android/server/net/NetworkIdentitySet.java
index 959a823..c48f430 100644
--- a/services/core/java/com/android/server/net/NetworkIdentitySet.java
+++ b/services/core/java/com/android/server/net/NetworkIdentitySet.java
@@ -91,6 +91,19 @@
         }
     }
 
+    /** @return whether any {@link NetworkIdentity} in this set is considered metered. */
+    public boolean isAnyMemberMetered() {
+        if (isEmpty()) {
+            return false;
+        }
+        for (NetworkIdentity ident : this) {
+            if (ident.getMetered()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** @return whether any {@link NetworkIdentity} in this set is considered roaming. */
     public boolean isAnyMemberRoaming() {
         if (isEmpty()) {
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index 673dd8f..c45b416 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -17,6 +17,8 @@
 package com.android.server.net;
 
 import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.ROAMING_YES;
 import static android.net.NetworkStats.SET_ALL;
@@ -242,6 +244,7 @@
                 entry.uid = key.uid;
                 entry.set = key.set;
                 entry.tag = key.tag;
+                entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO;
                 entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO;
                 entry.rxBytes = historyEntry.rxBytes;
                 entry.rxPackets = historyEntry.rxPackets;
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index bff68d8..3193974 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -21,20 +21,14 @@
 import android.content.pm.PackageStats;
 import android.os.Build;
 import android.os.IInstalld;
-import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
 import android.util.Slog;
 
-import com.android.internal.os.InstallerConnection;
-import com.android.internal.os.InstallerConnection.InstallerException;
 import com.android.server.SystemService;
 
 import dalvik.system.VMRuntime;
 
-import java.util.Arrays;
-
-public final class Installer extends SystemService {
+public class Installer extends SystemService {
     private static final String TAG = "Installer";
 
     /* ***************************************************************************
@@ -51,32 +45,29 @@
     public static final int DEXOPT_BOOTCOMPLETE   = 1 << 4;
     /** Hint that the dexopt type is profile-guided. */
     public static final int DEXOPT_PROFILE_GUIDED = 1 << 5;
-    /** This is an OTA update dexopt */
-    public static final int DEXOPT_OTA            = 1 << 6;
 
     // NOTE: keep in sync with installd
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
     public static final int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
 
-    private final InstallerConnection mInstaller;
-    private final IInstalld mInstalld;
+    private final boolean mIsolated;
 
+    // TODO: reconnect if installd restarts
+    private volatile IInstalld mInstalld;
     private volatile Object mWarnIfHeld;
 
     public Installer(Context context) {
-        super(context);
-        mInstaller = new InstallerConnection();
-        // TODO: reconnect if installd restarts
-        mInstalld = IInstalld.Stub.asInterface(ServiceManager.getService("installd"));
+        this(context, false);
     }
 
-    // Package-private installer that accepts a custom InstallerConnection. Used for
-    // OtaDexoptService.
-    Installer(Context context, InstallerConnection connection) {
+    /**
+     * @param isolated indicates if this object should <em>not</em> connect to
+     *            the real {@code installd}. All remote calls will be ignored
+     *            unless you extend this class and intercept them.
+     */
+    public Installer(Context context, boolean isolated) {
         super(context);
-        mInstaller = connection;
-        // TODO: reconnect if installd restarts
-        mInstalld = IInstalld.Stub.asInterface(ServiceManager.getService("installd"));
+        mIsolated = isolated;
     }
 
     /**
@@ -84,227 +75,238 @@
      * the given object.
      */
     public void setWarnIfHeld(Object warnIfHeld) {
-        mInstaller.setWarnIfHeld(warnIfHeld);
         mWarnIfHeld = warnIfHeld;
     }
 
     @Override
     public void onStart() {
-        Slog.i(TAG, "Waiting for installd to be ready.");
-        mInstaller.waitForConnection();
+        if (mIsolated) {
+            mInstalld = null;
+        } else {
+            mInstalld = IInstalld.Stub.asInterface(ServiceManager.getService("installd"));
+        }
     }
 
-    private void checkLock() {
+    /**
+     * Do several pre-flight checks before making a remote call.
+     *
+     * @return if the remote call should continue.
+     */
+    private boolean checkBeforeRemote() {
         if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
             Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
                     + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
         }
+        if (mIsolated) {
+            Slog.i(TAG, "Ignoring request because this installer is isolated");
+            return false;
+        } else {
+            return true;
+        }
     }
 
     public void createAppData(String uuid, String packageName, int userId, int flags, int appId,
             String seInfo, int targetSdkVersion) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
                     targetSdkVersion);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void restoreconAppData(String uuid, String packageName, int userId, int flags, int appId,
             String seInfo) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.restoreconAppData(uuid, packageName, userId, flags, appId, seInfo);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void migrateAppData(String uuid, String packageName, int userId, int flags)
             throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.migrateAppData(uuid, packageName, userId, flags);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void clearAppData(String uuid, String packageName, int userId, int flags,
             long ceDataInode) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.clearAppData(uuid, packageName, userId, flags, ceDataInode);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void destroyAppData(String uuid, String packageName, int userId, int flags,
             long ceDataInode) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.destroyAppData(uuid, packageName, userId, flags, ceDataInode);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void moveCompleteApp(String fromUuid, String toUuid, String packageName,
             String dataAppName, int appId, String seInfo, int targetSdkVersion)
             throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.moveCompleteApp(fromUuid, toUuid, packageName, dataAppName, appId, seInfo,
                     targetSdkVersion);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
-    public void getAppSize(String uuid, String pkgname, int userid, int flags, long ceDataInode,
+    public void getAppSize(String uuid, String packageName, int userId, int flags, long ceDataInode,
             String codePath, PackageStats stats) throws InstallerException {
-        final String[] res = mInstaller.execute("get_app_size", uuid, pkgname, userid, flags,
-                ceDataInode, codePath);
+        if (!checkBeforeRemote()) return;
         try {
-            stats.codeSize += Long.parseLong(res[1]);
-            stats.dataSize += Long.parseLong(res[2]);
-            stats.cacheSize += Long.parseLong(res[3]);
-        } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
-            throw new InstallerException("Invalid size result: " + Arrays.toString(res));
+            final long[] res = mInstalld.getAppSize(uuid, packageName, userId, flags, ceDataInode,
+                    codePath);
+            stats.codeSize += res[0];
+            stats.dataSize += res[1];
+            stats.cacheSize += res[2];
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public long getAppDataInode(String uuid, String packageName, int userId, int flags)
             throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return -1;
         try {
             return mInstalld.getAppDataInode(uuid, packageName, userId, flags);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
-    public void dexopt(String apkPath, int uid, String instructionSet, int dexoptNeeded,
-            int dexFlags, String compilerFilter, String volumeUuid, String sharedLibraries)
-            throws InstallerException {
-        assertValidInstructionSet(instructionSet);
-        mInstaller.dexopt(apkPath, uid, instructionSet, dexoptNeeded, dexFlags,
-                compilerFilter, volumeUuid, sharedLibraries);
-    }
-
-    public void dexopt(String apkPath, int uid, String pkgName, String instructionSet,
+    public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
-            String compilerFilter, String volumeUuid, String sharedLibraries)
+            String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries)
             throws InstallerException {
         assertValidInstructionSet(instructionSet);
-        mInstaller.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded,
-                outputPath, dexFlags, compilerFilter, volumeUuid, sharedLibraries);
+        if (!checkBeforeRemote()) return;
+        try {
+            mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
+                    dexFlags, compilerFilter, volumeUuid, sharedLibraries);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
     }
 
     public boolean mergeProfiles(int uid, String packageName) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return false;
         try {
             return mInstalld.mergeProfiles(uid, packageName);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public boolean dumpProfiles(int uid, String packageName, String codePaths)
             throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return false;
         try {
             return mInstalld.dumpProfiles(uid, packageName, codePaths);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void idmap(String targetApkPath, String overlayApkPath, int uid)
             throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.idmap(targetApkPath, overlayApkPath, uid);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void rmdex(String codePath, String instructionSet) throws InstallerException {
         assertValidInstructionSet(instructionSet);
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.rmdex(codePath, instructionSet);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void rmPackageDir(String packageDir) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.rmPackageDir(packageDir);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void clearAppProfiles(String packageName) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.clearAppProfiles(packageName);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void destroyAppProfiles(String packageName) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.destroyAppProfiles(packageName);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void createUserData(String uuid, int userId, int userSerial, int flags)
             throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.createUserData(uuid, userId, userSerial, flags);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void destroyUserData(String uuid, int userId, int flags) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.destroyUserData(uuid, userId, flags);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void markBootComplete(String instructionSet) throws InstallerException {
         assertValidInstructionSet(instructionSet);
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.markBootComplete(instructionSet);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void freeCache(String uuid, long freeStorageSize) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.freeCache(uuid, freeStorageSize);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
@@ -315,51 +317,51 @@
      */
     public void linkNativeLibraryDirectory(String uuid, String packageName, String nativeLibPath32,
             int userId) throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.linkNativeLibraryDirectory(uuid, packageName, nativeLibPath32, userId);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void createOatDir(String oatDir, String dexInstructionSet)
             throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.createOatDir(oatDir, dexInstructionSet);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void linkFile(String relativePath, String fromBase, String toBase)
             throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.linkFile(relativePath, fromBase, toBase);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void moveAb(String apkPath, String instructionSet, String outputPath)
             throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.moveAb(apkPath, instructionSet, outputPath);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
     public void deleteOdex(String apkPath, String instructionSet, String outputPath)
             throws InstallerException {
-        checkLock();
+        if (!checkBeforeRemote()) return;
         try {
             mInstalld.deleteOdex(apkPath, instructionSet, outputPath);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new InstallerException(e.getMessage());
+        } catch (Exception e) {
+            throw InstallerException.from(e);
         }
     }
 
@@ -372,4 +374,14 @@
         }
         throw new InstallerException("Invalid instruction set: " + instructionSet);
     }
+
+    public static class InstallerException extends Exception {
+        public InstallerException(String detailMessage) {
+            super(detailMessage);
+        }
+
+        public static InstallerException from(Exception e) throws InstallerException {
+            throw new InstallerException(e.toString());
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 689917c..60c83b4 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -16,11 +16,11 @@
 
 package com.android.server.pm;
 
-import static com.android.server.pm.Installer.DEXOPT_OTA;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.IOtaDexopt;
 import android.content.pm.PackageParser;
@@ -29,15 +29,17 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.storage.StorageManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
+
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.os.InstallerConnection;
-import com.android.internal.os.InstallerConnection.InstallerException;
+import com.android.server.pm.Installer.InstallerException;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -276,9 +278,27 @@
      */
     private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg,
             int compilationReason) {
-        // Use our custom connection that just collects the commands.
-        RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection();
-        Installer collectingInstaller = new Installer(mContext, collectingConnection);
+        // Intercept and collect dexopt requests
+        final List<String> commands = new ArrayList<String>();
+        final Installer collectingInstaller = new Installer(mContext, true) {
+            @Override
+            public void dexopt(String apkPath, int uid, @Nullable String pkgName,
+                    String instructionSet, int dexoptNeeded, @Nullable String outputPath,
+                    int dexFlags, String compilerFilter, @Nullable String volumeUuid,
+                    @Nullable String sharedLibraries) throws InstallerException {
+                commands.add(buildCommand("dexopt",
+                        apkPath,
+                        uid,
+                        pkgName,
+                        instructionSet,
+                        dexoptNeeded,
+                        outputPath,
+                        dexFlags,
+                        compilerFilter,
+                        volumeUuid,
+                        sharedLibraries));
+            }
+        };
 
         // Use the package manager install and install lock here for the OTA dex optimizer.
         PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
@@ -295,7 +315,7 @@
                 getCompilerFilterForReason(compilationReason),
                 null /* CompilerStats.PackageStats */);
 
-        return collectingConnection.commands;
+        return commands;
     }
 
     @Override
@@ -400,53 +420,33 @@
 
     private static class OTADexoptPackageDexOptimizer extends
             PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
-
         public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
                 Context context) {
             super(installer, installLock, context, "*otadexopt*");
         }
-
-        @Override
-        protected int adjustDexoptFlags(int dexoptFlags) {
-            // Add the OTA flag.
-            return dexoptFlags | DEXOPT_OTA;
-        }
-
     }
 
-    private static class RecordingInstallerConnection extends InstallerConnection {
-        public List<String> commands = new ArrayList<String>(1);
-
-        @Override
-        public void setWarnIfHeld(Object warnIfHeld) {
-            throw new IllegalStateException("Should not reach here");
+    /**
+     * Cook up argument list in the format that {@code installd} expects.
+     */
+    private static String buildCommand(Object... args) {
+        final StringBuilder builder = new StringBuilder();
+        for (Object arg : args) {
+            String escaped;
+            if (arg == null) {
+                escaped = "";
+            } else {
+                escaped = String.valueOf(arg);
+            }
+            if (escaped.indexOf('\0') != -1 || escaped.indexOf(' ') != -1 || "!".equals(escaped)) {
+                throw new IllegalArgumentException(
+                        "Invalid argument while executing " + Arrays.toString(args));
+            }
+            if (TextUtils.isEmpty(escaped)) {
+                escaped = "!";
+            }
+            builder.append(' ').append(escaped);
         }
-
-        @Override
-        public synchronized String transact(String cmd) {
-            commands.add(cmd);
-            return "0";
-        }
-
-        @Override
-        public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
-            throw new IllegalStateException("Should not reach here");
-        }
-
-        @Override
-        public boolean dumpProfiles(String gid, String packageName, String codePaths)
-                throws InstallerException {
-            throw new IllegalStateException("Should not reach here");
-        }
-
-        @Override
-        public void disconnect() {
-            throw new IllegalStateException("Should not reach here");
-        }
-
-        @Override
-        public void waitForConnection() {
-            throw new IllegalStateException("Should not reach here");
-        }
+        return builder.toString();
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index acdcc72..30ff32b 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -27,8 +27,8 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.os.InstallerConnection.InstallerException;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.pm.Installer.InstallerException;
 
 import java.io.File;
 import java.io.IOException;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2ece99f..c85e1d8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -72,10 +72,10 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
-import com.android.internal.os.InstallerConnection.InstallerException;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
+import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
 
 import java.io.File;
diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java
index f243e63..0e3f173 100644
--- a/services/core/java/com/android/server/pm/PackageManagerException.java
+++ b/services/core/java/com/android/server/pm/PackageManagerException.java
@@ -19,7 +19,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser.PackageParserException;
 
-import com.android.internal.os.InstallerConnection.InstallerException;
+import com.android.server.pm.Installer.InstallerException;
 
 /** {@hide} */
 public class PackageManagerException extends Exception {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9102fee..fb06f01 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -231,7 +231,6 @@
 import com.android.internal.content.PackageHelper;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.IParcelFileDescriptorFactory;
-import com.android.internal.os.InstallerConnection.InstallerException;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.Zygote;
 import com.android.internal.telephony.CarrierAppUtils;
@@ -250,6 +249,7 @@
 import com.android.server.SystemConfig;
 import com.android.server.Watchdog;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PermissionsState.PermissionState;
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
@@ -1140,6 +1140,8 @@
     final @NonNull String mServicesSystemSharedLibraryPackageName;
     final @NonNull String mSharedSystemSharedLibraryPackageName;
 
+    final boolean mPermissionReviewRequired;
+
     private final PackageUsage mPackageUsage = new PackageUsage();
     private final CompilerStats mCompilerStats = new CompilerStats();
 
@@ -2070,6 +2072,10 @@
         }
 
         mContext = context;
+
+        mPermissionReviewRequired = context.getResources().getBoolean(
+                R.bool.config_permissionReviewRequired);
+
         mFactoryTest = factoryTest;
         mOnlyCore = onlyCore;
         mMetrics = new DisplayMetrics();
@@ -2228,8 +2234,9 @@
                                     getCompilerFilterForReason(REASON_SHARED_APK),
                                     false /* newProfile */);
                             if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
-                                mInstaller.dexopt(lib, Process.SYSTEM_UID, dexCodeInstructionSet,
-                                        dexoptNeeded, DEXOPT_PUBLIC /*dexFlags*/,
+                                mInstaller.dexopt(lib, Process.SYSTEM_UID, "*",
+                                        dexCodeInstructionSet, dexoptNeeded, null,
+                                        DEXOPT_PUBLIC,
                                         getCompilerFilterForReason(REASON_SHARED_APK),
                                         StorageManager.UUID_PRIVATE_INTERNAL,
                                         SKIP_SHARED_LIBRARY_CHECK);
@@ -4026,7 +4033,7 @@
             // their permissions as always granted runtime ones since we need
             // to keep the review required permission flag per user while an
             // install permission's state is shared across all users.
-            if (Build.PERMISSIONS_REVIEW_REQUIRED
+            if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
                     && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
                     && bp.isRuntime()) {
                 return;
@@ -4137,7 +4144,7 @@
             // their permissions as always granted runtime ones since we need
             // to keep the review required permission flag per user while an
             // install permission's state is shared across all users.
-            if (Build.PERMISSIONS_REVIEW_REQUIRED
+            if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
                     && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
                     && bp.isRuntime()) {
                 return;
@@ -10018,7 +10025,8 @@
                     // their permissions as always granted runtime ones since we need
                     // to keep the review required permission flag per user while an
                     // install permission's state is shared across all users.
-                    if (!appSupportsRuntimePermissions && !Build.PERMISSIONS_REVIEW_REQUIRED) {
+                    if (!appSupportsRuntimePermissions && !mPermissionReviewRequired
+                            && !Build.PERMISSIONS_REVIEW_REQUIRED) {
                         // For legacy apps dangerous permissions are install time ones.
                         grant = GRANT_INSTALL;
                     } else if (origPermissions.hasInstallPermission(bp.name)) {
@@ -10104,7 +10112,7 @@
                                             changedRuntimePermissionUserIds, userId);
                                 }
                                 // If the app supports runtime permissions no need for a review.
-                                if (Build.PERMISSIONS_REVIEW_REQUIRED
+                                if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
                                         && appSupportsRuntimePermissions
                                         && (flags & PackageManager
                                                 .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
@@ -10113,7 +10121,8 @@
                                     changedRuntimePermissionUserIds = ArrayUtils.appendInt(
                                             changedRuntimePermissionUserIds, userId);
                                 }
-                            } else if (Build.PERMISSIONS_REVIEW_REQUIRED
+                            } else if ((mPermissionReviewRequired
+                                        || Build.PERMISSIONS_REVIEW_REQUIRED)
                                     && !appSupportsRuntimePermissions) {
                                 // For legacy apps that need a permission review, every new
                                 // runtime permission is granted but it is pending a review.
@@ -16708,7 +16717,7 @@
             // If permission review is enabled and this is a legacy app, mark the
             // permission as requiring a review as this is the initial state.
             int flags = 0;
-            if (Build.PERMISSIONS_REVIEW_REQUIRED
+            if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
                     && ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                 flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
             }
@@ -20577,7 +20586,7 @@
         // permissions to keep per user flag state whether review is needed.
         // Hence, if a new user is added we have to propagate dangerous
         // permission grants for these legacy apps.
-        if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+        if (mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
             updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
                     | UPDATE_PERMISSIONS_REPLACE_ALL);
         }
@@ -21031,7 +21040,7 @@
         public boolean isPermissionsReviewRequired(String packageName, int userId) {
             synchronized (mPackages) {
                 // If we do not support permission review, done.
-                if (!Build.PERMISSIONS_REVIEW_REQUIRED) {
+                if (!mPermissionReviewRequired && !Build.PERMISSIONS_REVIEW_REQUIRED) {
                     return false;
                 }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 335af08..39cae37 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -77,13 +77,13 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.InstallerConnection.InstallerException;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
 import com.android.server.backup.PreferredActivityBackupHelper;
+import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PackageManagerService.DumpState;
 import com.android.server.pm.PermissionsState.PermissionState;
 
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 44894ed..badee82 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -547,7 +547,7 @@
                             bluetooth.getState() == BluetoothAdapter.STATE_OFF;
                     if (!bluetoothOff) {
                         Log.w(TAG, "Disabling Bluetooth...");
-                        bluetooth.disable(false);  // disable but don't persist new state
+                        bluetooth.disable(mContext.getPackageName(), false);  // disable but don't persist new state
                     }
                 } catch (RemoteException ex) {
                     Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
diff --git a/services/core/proto/ipconnectivity.proto b/services/core/proto/ipconnectivity.proto
index e0d7f09..29b318f 100644
--- a/services/core/proto/ipconnectivity.proto
+++ b/services/core/proto/ipconnectivity.proto
@@ -53,6 +53,7 @@
 
   // The event type code of the probe, represented by constants defined in
   // android.net.metrics.IpReachabilityEvent.
+  // NUD_FAILED_ORGANIC and PROVISIONING_LOST_ORGANIC recorded since version 1.
   optional int32 event_type = 2;
 };
 
@@ -126,11 +127,12 @@
 
   // Lifetime duration in milliseconds of a DhcpClient state, or transition
   // time in milliseconds between specific pairs of DhcpClient's states.
-  // Only populated when state_transition is populated.
+  // Only populated since version 1, when state_transition is populated.
   optional int32 duration_ms = 4;
 }
 
 // Represents the generation of an Android Packet Filter program.
+// Since version 1.
 message ApfProgramEvent {
   // Lifetime of the program in seconds.
   optional int64 lifetime = 1;
@@ -154,6 +156,7 @@
 
 // Represents Router Advertisement listening statistics for an interface with
 // Android Packet Filter enabled.
+// Since version 1.
 message ApfStatistics {
   // The time interval in milliseconds these stastistics cover.
   optional int64 duration_ms = 1;
@@ -183,6 +186,7 @@
 
 // Represents the reception of a Router Advertisement packet for an interface
 // with Android Packet Filter enabled.
+// Since version 1.
 message RaEvent {
   // All lifetime values are expressed in seconds. The default value for an
   // option lifetime that was not present in the RA option list is -1.
@@ -271,4 +275,11 @@
 
   // The number of events that had to be dropped due to a full buffer.
   optional int32 dropped_events = 2;
+
+  // The version number of the metrics events being collected.
+  //  nyc-dev: not populated, implicitly 0
+  //  nyc-dr1: not populated, implicitly 1 (sailfish and marlin only)
+  //  nyc-mr1: not populated, implicitly 1
+  //  nyc-mr2: 2
+  optional int32 version = 3;
 };
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index a8356dc..83001df 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -194,8 +194,10 @@
             { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
 
     private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
-    private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
+    private static final int ICMP6_ROUTER_SOLICITATION = 133;
     private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
+    private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
+    private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
 
     // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
     private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
@@ -805,6 +807,8 @@
         //   if it's multicast and we're dropping multicast:
         //     drop
         //   pass
+        // if it's ICMPv6 RS to any:
+        //   drop
         // if it's ICMPv6 NA to ff02::1:
         //   drop
 
@@ -829,10 +833,12 @@
 
         // Add unsolicited multicast neighbor announcements filter
         String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
-        // If not neighbor announcements, skip unsolicited multicast NA filter
         gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
+        // Drop all router solicitations (b/32833400)
+        gen.addJumpIfR0Equals(ICMP6_ROUTER_SOLICITATION, gen.DROP_LABEL);
+        // If not neighbor announcements, skip filter.
         gen.addJumpIfR0NotEquals(ICMP6_NEIGHBOR_ANNOUNCEMENT, skipUnsolicitedMulticastNALabel);
-        // If to ff02::1, drop
+        // If to ff02::1, drop.
         // TODO: Drop only if they don't contain the address of on-link neighbours.
         gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
         gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS,
@@ -852,6 +858,7 @@
      * <li>Pass all non-ICMPv6 IPv6 packets,
      * <li>Pass all non-IPv4 and non-IPv6 packets,
      * <li>Drop IPv6 ICMPv6 NAs to ff02::1.
+     * <li>Drop IPv6 ICMPv6 RSs.
      * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows
      *     insertion of RA filters here, or if there aren't any, just passes the packets.
      * </ul>
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 3183930..2187c57 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -12,12 +12,12 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
+    easymocklib \
     frameworks-base-testutils \
     services.core \
     services.devicepolicy \
     services.net \
     services.usage \
-    easymocklib \
     guava \
     android-support-test \
     mockito-target \
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 8d36ac9..ef95fb8 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -16,17 +16,12 @@
 
 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.NetworkPolicy.CYCLE_NONE;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
-import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
-import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
 import static android.net.TrafficStats.KB_IN_BYTES;
@@ -34,28 +29,42 @@
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.Time.TIMEZONE_UTC;
+
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
-import static org.easymock.EasyMock.anyInt;
-import static org.easymock.EasyMock.anyLong;
-import static org.easymock.EasyMock.aryEq;
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.isA;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.app.INotificationManager;
-import android.app.IProcessObserver;
+import android.app.IUidObserver;
 import android.app.Notification;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
-import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkPolicyListener;
@@ -69,40 +78,48 @@
 import android.net.NetworkTemplate;
 import android.os.Binder;
 import android.os.INetworkManagementService;
-import android.os.MessageQueue.IdleHandler;
+import android.os.PowerManagerInternal;
 import android.os.UserHandle;
-import android.test.AndroidTestCase;
-import android.test.mock.MockPackageManager;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.text.format.Time;
+import android.util.Log;
 import android.util.TrustedTime;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.net.NetworkPolicyManagerService;
+
+import libcore.io.IoUtils;
+
 import com.google.common.util.concurrent.AbstractFuture;
 
-import org.easymock.Capture;
-import org.easymock.EasyMock;
-import org.easymock.IAnswer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.io.File;
-import java.util.Calendar;
+import java.util.ArrayList;
 import java.util.LinkedHashSet;
-import java.util.TimeZone;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.logging.Handler;
-
-import libcore.io.IoUtils;
 
 /**
  * Tests for {@link NetworkPolicyManagerService}.
  */
-@LargeTest
-public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class NetworkPolicyManagerServiceTest {
     private static final String TAG = "NetworkPolicyManagerServiceTest";
 
     private static final long TEST_START = 1194220800000L;
@@ -114,19 +131,19 @@
     private BroadcastInterceptingContext mServiceContext;
     private File mPolicyDir;
 
-    private IActivityManager mActivityManager;
-    private INetworkStatsService mStatsService;
-    private INetworkManagementService mNetworkManager;
-    private INetworkPolicyListener mPolicyListener;
-    private TrustedTime mTime;
-    private IConnectivityManager mConnManager;
-    private INotificationManager mNotifManager;
+    private @Mock IActivityManager mActivityManager;
+    private @Mock INetworkStatsService mStatsService;
+    private @Mock INetworkManagementService mNetworkManager;
+    private @Mock TrustedTime mTime;
+    private @Mock IConnectivityManager mConnManager;
+    private @Mock INotificationManager mNotifManager;
+    private @Mock PackageManager mPackageManager;
 
-    private NetworkPolicyManagerService mService;
-    private IProcessObserver mProcessObserver;
+    private IUidObserver mUidObserver;
     private INetworkManagementEventObserver mNetworkObserver;
 
-    private Binder mStubBinder = new Binder();
+    private NetworkPolicyListenerAnswer mPolicyListener;
+    private NetworkPolicyManagerService mService;
 
     private long mStartTime;
     private long mElapsedRealtime;
@@ -139,39 +156,30 @@
     private static final int UID_A = UserHandle.getUid(USER_ID, APP_ID_A);
     private static final int UID_B = UserHandle.getUid(USER_ID, APP_ID_B);
 
-    private static final int PID_1 = 400;
-    private static final int PID_2 = 401;
-    private static final int PID_3 = 402;
+    private static final String PKG_NAME_A = "name.is.A,pkg.A";
 
-    public void _setUp() throws Exception {
-        super.setUp();
+    @BeforeClass
+    public static void registerLocalServices() {
+        addLocalServiceMock(PowerManagerInternal.class);
+        addLocalServiceMock(DeviceIdleController.LocalService.class);
+        final UsageStatsManagerInternal usageStats =
+                addLocalServiceMock(UsageStatsManagerInternal.class);
+        when(usageStats.getIdleUidsForUser(anyInt())).thenReturn(new int[]{});
+    }
+
+    @Before
+    public void callSystemReady() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        final Context context = InstrumentationRegistry.getContext();
 
         setCurrentTimeMillis(TEST_START);
 
         // intercept various broadcasts, and pretend that uids have packages
-        mServiceContext = new BroadcastInterceptingContext(getContext()) {
+        mServiceContext = new BroadcastInterceptingContext(context) {
             @Override
             public PackageManager getPackageManager() {
-                return new MockPackageManager() {
-                    @Override
-                    public String[] getPackagesForUid(int uid) {
-                        return new String[] { "com.example" };
-                    }
-
-                    @Override
-                    public PackageInfo getPackageInfo(String packageName, int flags) {
-                        final PackageInfo info = new PackageInfo();
-                        final Signature signature;
-                        if ("android".equals(packageName)) {
-                            signature = new Signature("F00D");
-                        } else {
-                            signature = new Signature("DEAD");
-                        }
-                        info.signatures = new Signature[] { signature };
-                        return info;
-                    }
-
-                };
+                return mPackageManager;
             }
 
             @Override
@@ -180,229 +188,112 @@
             }
         };
 
-        mPolicyDir = getContext().getFilesDir();
+        mPolicyDir = context.getFilesDir();
         if (mPolicyDir.exists()) {
             IoUtils.deleteContents(mPolicyDir);
         }
 
-        mActivityManager = createMock(IActivityManager.class);
-        mStatsService = createMock(INetworkStatsService.class);
-        mNetworkManager = createMock(INetworkManagementService.class);
-        mPolicyListener = createMock(INetworkPolicyListener.class);
-        mTime = createMock(TrustedTime.class);
-        mConnManager = createMock(IConnectivityManager.class);
-        mNotifManager = createMock(INotificationManager.class);
+        doAnswer(new Answer<Void>() {
 
-        mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager,
-                mStatsService, mNetworkManager, mTime, mPolicyDir, true);
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                mUidObserver = (IUidObserver) invocation.getArguments()[0];
+                Log.d(TAG, "set mUidObserver to " + mUidObserver);
+                return null;
+            }
+        }).when(mActivityManager).registerUidObserver(any(), anyInt());
+
+        mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager, mStatsService,
+                mNetworkManager, mTime, mPolicyDir, true);
         mService.bindConnectivityManager(mConnManager);
         mService.bindNotificationManager(mNotifManager);
+        mPolicyListener = new NetworkPolicyListenerAnswer(mService);
 
-        // RemoteCallbackList needs a binder to use as key
-        expect(mPolicyListener.asBinder()).andReturn(mStubBinder).atLeastOnce();
-        replay();
-        mService.registerListener(mPolicyListener);
-        verifyAndReset();
+        // Sets some common expectations.
+        when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenAnswer(
+                new Answer<PackageInfo>() {
 
-        // catch IProcessObserver during systemReady()
-        final Capture<IProcessObserver> processObserver = new Capture<IProcessObserver>();
-        mActivityManager.registerProcessObserver(capture(processObserver));
-        expectLastCall().atLeastOnce();
-
-        // catch INetworkManagementEventObserver during systemReady()
-        final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
-                INetworkManagementEventObserver>();
-        mNetworkManager.registerObserver(capture(networkObserver));
-        expectLastCall().atLeastOnce();
-
-        expect(mNetworkManager.isBandwidthControlEnabled()).andReturn(true).atLeastOnce();
+                    @Override
+                    public PackageInfo answer(InvocationOnMock invocation) throws Throwable {
+                        final String packageName = (String) invocation.getArguments()[0];
+                        final PackageInfo info = new PackageInfo();
+                        final Signature signature;
+                        if ("android".equals(packageName)) {
+                            signature = new Signature("F00D");
+                        } else {
+                            signature = new Signature("DEAD");
+                        }
+                        info.signatures = new Signature[] {
+                            signature
+                        };
+                        return info;
+                    }
+                });
+        when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(new ApplicationInfo());
+        when(mPackageManager.getPackagesForUid(UID_A)).thenReturn(new String[] {PKG_NAME_A});
+        when(mNetworkManager.isBandwidthControlEnabled()).thenReturn(true);
         expectCurrentTime();
 
-        replay();
+        // Prepare NPMS.
         mService.systemReady();
-        verifyAndReset();
 
-        mProcessObserver = processObserver.getValue();
+        // catch INetworkManagementEventObserver during systemReady()
+        ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
+              ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
+        verify(mNetworkManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
-
     }
 
-    public void _tearDown() throws Exception {
+    @After
+    public void removeFiles() throws Exception {
         for (File file : mPolicyDir.listFiles()) {
             file.delete();
         }
-
-        mServiceContext = null;
-        mPolicyDir = null;
-
-        mActivityManager = null;
-        mStatsService = null;
-        mPolicyListener = null;
-        mTime = null;
-
-        mService = null;
-        mProcessObserver = null;
-
-        super.tearDown();
     }
 
-    @Suppress
-    public void testPolicyChangeTriggersBroadcast() throws Exception {
+    @After
+    public void unregisterLocalServices() throws Exception {
+        // Registered by NetworkPolicyManagerService's constructor.
+        LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
+    }
+
+    // NOTE: testPolicyChangeTriggersListener() and testUidForeground() are too superficial, they
+    // don't check for side-effects (like calls to NetworkManagementService) neither cover all
+    // different modes (Data Saver, Battery Saver, Doze, App idle, etc...).
+    // These scenarios are extensively tested on CTS' HostsideRestrictBackgroundNetworkTests.
+
+    @Test
+    public void testPolicyChangeTriggersListener() throws Exception {
+        mPolicyListener.expect().onRestrictBackgroundBlacklistChanged(anyInt(), anyBoolean());
+
         mService.setUidPolicy(APP_ID_A, POLICY_NONE);
-
-        // change background policy and expect broadcast
-        final Future<Intent> backgroundChanged = mServiceContext.nextBroadcastIntent(
-                ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
-
         mService.setUidPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
 
-        backgroundChanged.get();
+        mPolicyListener.waitAndVerify().onRestrictBackgroundBlacklistChanged(APP_ID_A, true);
     }
 
-    @Suppress
-    public void testPidForegroundCombined() throws Exception {
-        IdleFuture idle;
-
-        // push all uid into background
-        idle = expectIdle();
-        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
-        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
-        mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, false);
-        idle.get();
+    @Test
+    public void testUidForeground() throws Exception {
+        // push all uids into background
+        mUidObserver.onUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE);
+        mUidObserver.onUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_SERVICE);
         assertFalse(mService.isUidForeground(UID_A));
         assertFalse(mService.isUidForeground(UID_B));
 
-        // push one of the shared pids into foreground
-        idle = expectIdle();
-        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
-        idle.get();
+        // push one of the uids into foreground
+        mUidObserver.onUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_TOP);
         assertTrue(mService.isUidForeground(UID_A));
         assertFalse(mService.isUidForeground(UID_B));
 
         // and swap another uid into foreground
-        idle = expectIdle();
-        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
-        mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, true);
-        idle.get();
+        mUidObserver.onUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE);
+        mUidObserver.onUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_TOP);
         assertFalse(mService.isUidForeground(UID_A));
         assertTrue(mService.isUidForeground(UID_B));
-
-        // push both pid into foreground
-        idle = expectIdle();
-        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
-        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
-        idle.get();
-        assertTrue(mService.isUidForeground(UID_A));
-
-        // pull one out, should still be foreground
-        idle = expectIdle();
-        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
-        idle.get();
-        assertTrue(mService.isUidForeground(UID_A));
-
-        // pull final pid out, should now be background
-        idle = expectIdle();
-        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
-        idle.get();
-        assertFalse(mService.isUidForeground(UID_A));
     }
 
-    @Suppress
-    public void testPolicyNone() throws Exception {
-        Future<Void> future;
-
-        expectSetUidMeteredNetworkBlacklist(UID_A, false);
-        expectSetUidForeground(UID_A, true);
-        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
-        replay();
-        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
-        future.get();
-        verifyAndReset();
-
-        // POLICY_NONE should RULE_ALLOW in foreground
-        expectSetUidMeteredNetworkBlacklist(UID_A, false);
-        expectSetUidForeground(UID_A, true);
-        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
-        replay();
-        mService.setUidPolicy(APP_ID_A, POLICY_NONE);
-        future.get();
-        verifyAndReset();
-
-        // POLICY_NONE should RULE_ALLOW in background
-        expectSetUidMeteredNetworkBlacklist(UID_A, false);
-        expectSetUidForeground(UID_A, false);
-        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
-        replay();
-        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
-        future.get();
-        verifyAndReset();
-    }
-
-    @Suppress
-    public void testPolicyReject() throws Exception {
-        Future<Void> future;
-
-        // POLICY_REJECT should RULE_ALLOW in background
-        expectSetUidMeteredNetworkBlacklist(UID_A, true);
-        expectSetUidForeground(UID_A, false);
-        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
-        replay();
-        mService.setUidPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
-        future.get();
-        verifyAndReset();
-
-        // POLICY_REJECT should RULE_ALLOW in foreground
-        expectSetUidMeteredNetworkBlacklist(UID_A, false);
-        expectSetUidForeground(UID_A, true);
-        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
-        replay();
-        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
-        future.get();
-        verifyAndReset();
-
-        // POLICY_REJECT should RULE_REJECT in background
-        expectSetUidMeteredNetworkBlacklist(UID_A, true);
-        expectSetUidForeground(UID_A, false);
-        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
-        replay();
-        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
-        future.get();
-        verifyAndReset();
-    }
-
-    @Suppress
-    public void testPolicyRejectAddRemove() throws Exception {
-        Future<Void> future;
-
-        // POLICY_NONE should have RULE_ALLOW in background
-        expectSetUidMeteredNetworkBlacklist(UID_A, false);
-        expectSetUidForeground(UID_A, false);
-        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
-        replay();
-        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
-        mService.setUidPolicy(APP_ID_A, POLICY_NONE);
-        future.get();
-        verifyAndReset();
-
-        // adding POLICY_REJECT should cause RULE_REJECT
-        expectSetUidMeteredNetworkBlacklist(UID_A, true);
-        expectSetUidForeground(UID_A, false);
-        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
-        replay();
-        mService.setUidPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
-        future.get();
-        verifyAndReset();
-
-        // removing POLICY_REJECT should return us to RULE_ALLOW
-        expectSetUidMeteredNetworkBlacklist(UID_A, false);
-        expectSetUidForeground(UID_A, false);
-        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
-        replay();
-        mService.setUidPolicy(APP_ID_A, POLICY_NONE);
-        future.get();
-        verifyAndReset();
-    }
-
+    @Test
     public void testLastCycleBoundaryThisMonth() throws Exception {
         // assume cycle day of "5th", which should be in same month
         final long currentTime = parseTime("2007-11-14T00:00:00.000Z");
@@ -414,6 +305,7 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
+    @Test
     public void testLastCycleBoundaryLastMonth() throws Exception {
         // assume cycle day of "20th", which should be in last month
         final long currentTime = parseTime("2007-11-14T00:00:00.000Z");
@@ -425,6 +317,7 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
+    @Test
     public void testLastCycleBoundaryThisMonthFebruary() throws Exception {
         // assume cycle day of "30th" in february; should go to january
         final long currentTime = parseTime("2007-02-14T00:00:00.000Z");
@@ -436,6 +329,7 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
+    @Test
     public void testLastCycleBoundaryLastMonthFebruary() throws Exception {
         // assume cycle day of "30th" in february, which should clamp
         final long currentTime = parseTime("2007-03-14T00:00:00.000Z");
@@ -447,6 +341,7 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
+    @Test
     public void testCycleBoundaryLeapYear() throws Exception {
         final NetworkPolicy policy = new NetworkPolicy(
                 sTemplateWifi, 29, TIMEZONE_UTC, 1024L, 1024L, false);
@@ -470,6 +365,7 @@
                 computeNextCycleBoundary(parseTime("2007-03-14T00:00:00.000Z"), policy));
     }
 
+    @Test
     public void testNextCycleTimezoneAfterUtc() throws Exception {
         // US/Central is UTC-6
         final NetworkPolicy policy = new NetworkPolicy(
@@ -478,6 +374,7 @@
                 computeNextCycleBoundary(parseTime("2012-01-05T00:00:00.000Z"), policy));
     }
 
+    @Test
     public void testNextCycleTimezoneBeforeUtc() throws Exception {
         // Israel is UTC+2
         final NetworkPolicy policy = new NetworkPolicy(
@@ -486,6 +383,7 @@
                 computeNextCycleBoundary(parseTime("2012-01-05T00:00:00.000Z"), policy));
     }
 
+    @Test
     public void testNextCycleSane() throws Exception {
         final NetworkPolicy policy = new NetworkPolicy(
                 sTemplateWifi, 31, TIMEZONE_UTC, WARNING_DISABLED, LIMIT_DISABLED, false);
@@ -501,6 +399,7 @@
         }
     }
 
+    @Test
     public void testLastCycleSane() throws Exception {
         final NetworkPolicy policy = new NetworkPolicy(
                 sTemplateWifi, 31, TIMEZONE_UTC, WARNING_DISABLED, LIMIT_DISABLED, false);
@@ -516,6 +415,7 @@
         }
     }
 
+    @Test
     public void testCycleTodayJanuary() throws Exception {
         final NetworkPolicy policy = new NetworkPolicy(
                 sTemplateWifi, 14, "US/Pacific", 1024L, 1024L, false);
@@ -535,6 +435,7 @@
                 computeLastCycleBoundary(parseTime("2013-01-14T15:11:00.000-08:00"), policy));
     }
 
+    @Test
     public void testLastCycleBoundaryDST() throws Exception {
         final long currentTime = parseTime("1989-01-02T07:30:00.000");
         final long expectedCycle = parseTime("1988-12-03T02:00:00.000Z");
@@ -545,11 +446,10 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
-    @Suppress
+    @Test
     public void testNetworkPolicyAppliedCycleLastMonth() throws Exception {
         NetworkState[] state = null;
         NetworkStats stats = null;
-        Future<Void> future;
 
         final long TIME_FEB_15 = 1171497600000L;
         final long TIME_MAR_10 = 1173484800000L;
@@ -560,75 +460,40 @@
         // first, pretend that wifi network comes online. no policy active,
         // which means we shouldn't push limit to interface.
         state = new NetworkState[] { buildWifi() };
-        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+        when(mConnManager.getAllNetworkState()).thenReturn(state);
         expectCurrentTime();
-        expectClearNotifications();
-        expectAdvisePersistThreshold();
-        future = expectMeteredIfacesChanged();
 
-        replay();
+        mPolicyListener.expect().onMeteredIfacesChanged(any());
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
-        future.get();
-        verifyAndReset();
+        mPolicyListener.waitAndVerify().onMeteredIfacesChanged(any());
 
         // now change cycle to be on 15th, and test in early march, to verify we
         // pick cycle day in previous month.
-        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+        when(mConnManager.getAllNetworkState()).thenReturn(state);
         expectCurrentTime();
 
         // pretend that 512 bytes total have happened
         stats = new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 256L, 2L, 256L, 2L);
-        expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, TIME_MAR_10))
-                .andReturn(stats.getTotalBytes()).atLeastOnce();
-        expectPolicyDataEnable(TYPE_WIFI, true);
+        when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, TIME_MAR_10))
+                .thenReturn(stats.getTotalBytes());
 
-        // TODO: consider making strongly ordered mock
-        expectRemoveInterfaceQuota(TEST_IFACE);
-        expectSetInterfaceQuota(TEST_IFACE, (2 * MB_IN_BYTES) - 512);
-
-        expectClearNotifications();
-        expectAdvisePersistThreshold();
-        future = expectMeteredIfacesChanged(TEST_IFACE);
-
-        replay();
+        mPolicyListener.expect().onMeteredIfacesChanged(any());
         setNetworkPolicies(new NetworkPolicy(
                 sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, false));
-        future.get();
-        verifyAndReset();
+        mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
+
+        // TODO: consider making strongly ordered mock
+        verifyPolicyDataEnable(TYPE_WIFI, true);
+        verifyRemoveInterfaceQuota(TEST_IFACE);
+        verifySetInterfaceQuota(TEST_IFACE, (2 * MB_IN_BYTES) - 512);
     }
 
-    @Suppress
-    public void testUidRemovedPolicyCleared() throws Exception {
-        Future<Void> future;
-
-        // POLICY_REJECT should RULE_REJECT in background
-        expectSetUidMeteredNetworkBlacklist(UID_A, true);
-        expectSetUidForeground(UID_A, false);
-        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
-        replay();
-        mService.setUidPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
-        future.get();
-        verifyAndReset();
-
-        // uninstall should clear RULE_REJECT
-        expectSetUidMeteredNetworkBlacklist(UID_A, false);
-        expectSetUidForeground(UID_A, false);
-        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
-        replay();
-        final Intent intent = new Intent(ACTION_UID_REMOVED);
-        intent.putExtra(EXTRA_UID, UID_A);
-        mServiceContext.sendBroadcast(intent);
-        future.get();
-        verifyAndReset();
-    }
-
-    @Suppress
+    @Test
     public void testOverWarningLimitNotification() throws Exception {
         NetworkState[] state = null;
         NetworkStats stats = null;
-        Future<Void> future;
-        Future<String> tagFuture;
+        Future<String> tagFuture = null;
 
         final long TIME_FEB_15 = 1171497600000L;
         final long TIME_MAR_10 = 1173484800000L;
@@ -643,20 +508,15 @@
 
         {
             expectCurrentTime();
-            expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
-            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
-                    .andReturn(stats.getTotalBytes()).atLeastOnce();
-            expectPolicyDataEnable(TYPE_WIFI, true);
+            when(mConnManager.getAllNetworkState()).thenReturn(state);
+            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
+                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
 
-            expectClearNotifications();
-            expectAdvisePersistThreshold();
-            future = expectMeteredIfacesChanged();
-
-            replay();
+            mPolicyListener.expect().onMeteredIfacesChanged(any());
             setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1
                     * MB_IN_BYTES, 2 * MB_IN_BYTES, false));
-            future.get();
-            verifyAndReset();
+            mPolicyListener.waitAndVerify().onMeteredIfacesChanged(any());
+            verifyPolicyDataEnable(TYPE_WIFI, true);
         }
 
         // bring up wifi network
@@ -667,22 +527,17 @@
 
         {
             expectCurrentTime();
-            expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
-            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
-                    .andReturn(stats.getTotalBytes()).atLeastOnce();
-            expectPolicyDataEnable(TYPE_WIFI, true);
+            when(mConnManager.getAllNetworkState()).thenReturn(state);
+            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
+                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
 
-            expectRemoveInterfaceQuota(TEST_IFACE);
-            expectSetInterfaceQuota(TEST_IFACE, 2 * MB_IN_BYTES);
-
-            expectClearNotifications();
-            expectAdvisePersistThreshold();
-            future = expectMeteredIfacesChanged(TEST_IFACE);
-
-            replay();
+            mPolicyListener.expect().onMeteredIfacesChanged(any());
             mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
-            future.get();
-            verifyAndReset();
+            mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
+
+            verifyPolicyDataEnable(TYPE_WIFI, true);
+            verifyRemoveInterfaceQuota(TEST_IFACE);
+            verifySetInterfaceQuota(TEST_IFACE, 2 * MB_IN_BYTES);
         }
 
         // go over warning, which should kick notification
@@ -692,18 +547,15 @@
 
         {
             expectCurrentTime();
-            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
-                    .andReturn(stats.getTotalBytes()).atLeastOnce();
-            expectPolicyDataEnable(TYPE_WIFI, true);
-
-            expectForceUpdate();
-            expectClearNotifications();
+            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
+                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
             tagFuture = expectEnqueueNotification();
 
-            replay();
             mNetworkObserver.limitReached(null, TEST_IFACE);
+
             assertNotificationType(TYPE_WARNING, tagFuture.get());
-            verifyAndReset();
+            verifyPolicyDataEnable(TYPE_WIFI, true);
+
         }
 
         // go over limit, which should kick notification and dialog
@@ -713,18 +565,14 @@
 
         {
             expectCurrentTime();
-            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
-                    .andReturn(stats.getTotalBytes()).atLeastOnce();
-            expectPolicyDataEnable(TYPE_WIFI, false);
-
-            expectForceUpdate();
-            expectClearNotifications();
+            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
+                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
             tagFuture = expectEnqueueNotification();
 
-            replay();
             mNetworkObserver.limitReached(null, TEST_IFACE);
+
             assertNotificationType(TYPE_LIMIT, tagFuture.get());
-            verifyAndReset();
+            verifyPolicyDataEnable(TYPE_WIFI, false);
         }
 
         // now snooze policy, which should remove quota
@@ -732,35 +580,28 @@
 
         {
             expectCurrentTime();
-            expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
-            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
-                    .andReturn(stats.getTotalBytes()).atLeastOnce();
-            expectPolicyDataEnable(TYPE_WIFI, true);
-
-            // snoozed interface still has high quota so background data is
-            // still restricted.
-            expectRemoveInterfaceQuota(TEST_IFACE);
-            expectSetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
-            expectAdvisePersistThreshold();
-            expectMeteredIfacesChanged(TEST_IFACE);
-
-            future = expectClearNotifications();
+            when(mConnManager.getAllNetworkState()).thenReturn(state);
+            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
+                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
             tagFuture = expectEnqueueNotification();
 
-            replay();
+            mPolicyListener.expect().onMeteredIfacesChanged(any());
             mService.snoozeLimit(sTemplateWifi);
+            mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
+
             assertNotificationType(TYPE_LIMIT_SNOOZED, tagFuture.get());
-            future.get();
-            verifyAndReset();
+            // snoozed interface still has high quota so background data is
+            // still restricted.
+            verifyRemoveInterfaceQuota(TEST_IFACE);
+            verifySetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
+            verifyPolicyDataEnable(TYPE_WIFI, true);
         }
     }
 
-    @Suppress
+    @Test
     public void testMeteredNetworkWithoutLimit() throws Exception {
         NetworkState[] state = null;
         NetworkStats stats = null;
-        Future<Void> future;
-        Future<String> tagFuture;
 
         final long TIME_FEB_15 = 1171497600000L;
         final long TIME_MAR_10 = 1173484800000L;
@@ -775,24 +616,19 @@
 
         {
             expectCurrentTime();
-            expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
-            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
-                    .andReturn(stats.getTotalBytes()).atLeastOnce();
-            expectPolicyDataEnable(TYPE_WIFI, true);
+            when(mConnManager.getAllNetworkState()).thenReturn(state);
+            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
+                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
 
-            expectRemoveInterfaceQuota(TEST_IFACE);
-            expectSetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
-
-            expectClearNotifications();
-            expectAdvisePersistThreshold();
-            future = expectMeteredIfacesChanged(TEST_IFACE);
-
-            replay();
+            mPolicyListener.expect().onMeteredIfacesChanged(any());
             setNetworkPolicies(new NetworkPolicy(
                     sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, LIMIT_DISABLED,
                     true));
-            future.get();
-            verifyAndReset();
+            mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
+
+            verifyPolicyDataEnable(TYPE_WIFI, true);
+            verifyRemoveInterfaceQuota(TEST_IFACE);
+            verifySetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
         }
     }
 
@@ -815,87 +651,36 @@
     }
 
     private void expectCurrentTime() throws Exception {
-        expect(mTime.forceRefresh()).andReturn(false).anyTimes();
-        expect(mTime.hasCache()).andReturn(true).anyTimes();
-        expect(mTime.currentTimeMillis()).andReturn(currentTimeMillis()).anyTimes();
-        expect(mTime.getCacheAge()).andReturn(0L).anyTimes();
-        expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
-    }
-
-    private void expectForceUpdate() throws Exception {
-        mStatsService.forceUpdate();
-        expectLastCall().atLeastOnce();
-    }
-
-    private Future<Void> expectClearNotifications() throws Exception {
-        final FutureAnswer future = new FutureAnswer();
-        mNotifManager.cancelNotificationWithTag(
-                isA(String.class), isA(String.class), anyInt(), anyInt());
-        expectLastCall().andAnswer(future).anyTimes();
-        return future;
+        when(mTime.forceRefresh()).thenReturn(false);
+        when(mTime.hasCache()).thenReturn(true);
+        when(mTime.currentTimeMillis()).thenReturn(currentTimeMillis());
+        when(mTime.getCacheAge()).thenReturn(0L);
+        when(mTime.getCacheCertainty()).thenReturn(0L);
     }
 
     private Future<String> expectEnqueueNotification() throws Exception {
-        final FutureCapture<String> tag = new FutureCapture<String>();
-        mNotifManager.enqueueNotificationWithTag(isA(String.class), isA(String.class),
-                capture(tag.capture), anyInt(),
-                isA(Notification.class), isA(int[].class), UserHandle.myUserId());
-        return tag;
+        final FutureAnswer<String> futureAnswer = new FutureAnswer<String>(2);
+        doAnswer(futureAnswer).when(mNotifManager).enqueueNotificationWithTag(
+                anyString(), anyString(), anyString() /* capture here (index 2)*/,
+                anyInt(), isA(Notification.class), isA(int[].class), anyInt());
+        return futureAnswer;
     }
 
-    private void expectSetInterfaceQuota(String iface, long quotaBytes) throws Exception {
-        mNetworkManager.setInterfaceQuota(iface, quotaBytes);
-        expectLastCall().atLeastOnce();
+    private void verifySetInterfaceQuota(String iface, long quotaBytes) throws Exception {
+        verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(iface, quotaBytes);
     }
 
-    private void expectRemoveInterfaceQuota(String iface) throws Exception {
-        mNetworkManager.removeInterfaceQuota(iface);
-        expectLastCall().atLeastOnce();
+    private void verifyRemoveInterfaceQuota(String iface) throws Exception {
+        verify(mNetworkManager, atLeastOnce()).removeInterfaceQuota(iface);
     }
 
-    private void expectSetInterfaceAlert(String iface, long alertBytes) throws Exception {
-        mNetworkManager.setInterfaceAlert(iface, alertBytes);
-        expectLastCall().atLeastOnce();
-    }
-
-    private void expectRemoveInterfaceAlert(String iface) throws Exception {
-        mNetworkManager.removeInterfaceAlert(iface);
-        expectLastCall().atLeastOnce();
-    }
-
-    private void expectSetUidMeteredNetworkBlacklist(int uid, boolean rejectOnQuotaInterfaces)
-            throws Exception {
-        mNetworkManager.setUidMeteredNetworkBlacklist(uid, rejectOnQuotaInterfaces);
-        expectLastCall().atLeastOnce();
-    }
-
-    private void expectSetUidForeground(int uid, boolean uidForeground) throws Exception {
-        mStatsService.setUidForeground(uid, uidForeground);
-        expectLastCall().atLeastOnce();
-    }
-
-    private Future<Void> expectRulesChanged(int uid, int policy) throws Exception {
-        final FutureAnswer future = new FutureAnswer();
-        mPolicyListener.onUidRulesChanged(eq(uid), eq(policy));
-        expectLastCall().andAnswer(future);
-        return future;
-    }
-
-    private Future<Void> expectMeteredIfacesChanged(String... ifaces) throws Exception {
-        final FutureAnswer future = new FutureAnswer();
-        mPolicyListener.onMeteredIfacesChanged(aryEq(ifaces));
-        expectLastCall().andAnswer(future);
-        return future;
-    }
-
-    private Future<Void> expectPolicyDataEnable(int type, boolean enabled) throws Exception {
+    private Future<Void> verifyPolicyDataEnable(int type, boolean enabled) throws Exception {
         // TODO: bring back this test
         return null;
     }
 
-    private void expectAdvisePersistThreshold() throws Exception {
-        mStatsService.advisePersistThreshold(anyLong());
-        expectLastCall().anyTimes();
+    private void verifyAdvisePersistThreshold() throws Exception {
+        verify(mStatsService).advisePersistThreshold(anyLong());
     }
 
     private static class TestAbstractFuture<T> extends AbstractFuture<T> {
@@ -909,50 +694,21 @@
         }
     }
 
-    private static class FutureAnswer extends TestAbstractFuture<Void> implements IAnswer<Void> {
+    private static class FutureAnswer<T> extends TestAbstractFuture<T> implements Answer<Void> {
+        private final int index;
+
+        FutureAnswer(int index) {
+            this.index = index;
+        }
         @Override
-        public Void answer() {
-            set(null);
+        public Void answer(InvocationOnMock invocation) throws Throwable {
+            @SuppressWarnings("unchecked")
+            T captured = (T) invocation.getArguments()[index];
+            set(captured);
             return null;
         }
     }
 
-    private static class FutureCapture<T> extends TestAbstractFuture<T> {
-        public Capture<T> capture = new Capture<T>() {
-            @Override
-            public void setValue(T value) {
-                super.setValue(value);
-                set(value);
-            }
-        };
-    }
-
-    private static class IdleFuture extends AbstractFuture<Void> implements IdleHandler {
-        @Override
-        public Void get() throws InterruptedException, ExecutionException {
-            try {
-                return get(5, TimeUnit.SECONDS);
-            } catch (TimeoutException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        @Override
-        public boolean queueIdle() {
-            set(null);
-            return false;
-        }
-    }
-
-    /**
-     * Wait until {@link #mService} internal {@link Handler} is idle.
-     */
-    private IdleFuture expectIdle() {
-        final IdleFuture future = new IdleFuture();
-        mService.addIdleHandler(future);
-        return future;
-    }
-
     private static void assertTimeEquals(long expected, long actual) {
         if (expected != actual) {
             fail("expected " + formatTime(expected) + " but was actually " + formatTime(actual));
@@ -980,7 +736,7 @@
     }
 
     private static void assertNotificationType(int expected, String actualTag) {
-        assertEquals(
+        assertEquals("notification type mismatch for '" + actualTag +"'",
                 Integer.toString(expected), actualTag.substring(actualTag.lastIndexOf(':') + 1));
     }
 
@@ -1001,15 +757,59 @@
         mElapsedRealtime += duration;
     }
 
-    private void replay() {
-        EasyMock.replay(mActivityManager, mStatsService, mPolicyListener, mNetworkManager, mTime,
-                mConnManager, mNotifManager);
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> T addLocalServiceMock(Class<T> clazz) {
+        final T mock = mock(clazz);
+        LocalServices.addService(clazz, mock);
+        return mock;
     }
 
-    private void verifyAndReset() {
-        EasyMock.verify(mActivityManager, mStatsService, mPolicyListener, mNetworkManager, mTime,
-                mConnManager, mNotifManager);
-        EasyMock.reset(mActivityManager, mStatsService, mPolicyListener, mNetworkManager, mTime,
-                mConnManager, mNotifManager);
+    /**
+     * Custom Mockito answer used to verify async {@link INetworkPolicyListener} calls.
+     *
+     * <p>Typical usage:
+     * <pre><code>
+     *    mPolicyListener.expect().someCallback(any());
+     *    // do something on objects under test
+     *    mPolicyListener.waitAndVerify().someCallback(eq(expectedValue));
+     * </code></pre>
+     */
+    final class NetworkPolicyListenerAnswer implements Answer<Void> {
+        private CountDownLatch latch;
+        private final INetworkPolicyListener listener;
+
+        NetworkPolicyListenerAnswer(NetworkPolicyManagerService service) {
+            this.listener = mock(INetworkPolicyListener.class);
+            // RemoteCallbackList needs a binder to use as key
+            when(listener.asBinder()).thenReturn(new Binder());
+            service.registerListener(listener);
+        }
+
+        @Override
+        public Void answer(InvocationOnMock invocation) throws Throwable {
+            Log.d(TAG,"counting down on answer: " + invocation);
+            latch.countDown();
+            return null;
+        }
+
+        INetworkPolicyListener expect() {
+            assertNull("expect() called before waitAndVerify()", latch);
+            latch = new CountDownLatch(1);
+            return doAnswer(this).when(listener);
+        }
+
+        INetworkPolicyListener waitAndVerify() {
+            assertNotNull("waitAndVerify() called before expect()", latch);
+            try {
+                assertTrue("callback not called in 5 seconds", latch.await(5, TimeUnit.SECONDS));
+            } catch (InterruptedException e) {
+                fail("Thread interrupted before callback called");
+            } finally {
+                latch = null;
+            }
+            return verify(listener, atLeastOnce());
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 0139671..50911cb 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import static android.net.NetworkScoreManager.CACHE_FILTER_NONE;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
@@ -26,6 +28,7 @@
 import static org.mockito.Matchers.anyListOf;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -70,6 +73,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 /**
@@ -177,7 +181,8 @@
     public void testUpdateScores_oneRegisteredCache() throws RemoteException {
         when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
 
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
+                mNetworkScoreCache, CACHE_FILTER_NONE);
 
         mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
 
@@ -191,9 +196,10 @@
     public void testUpdateScores_twoRegisteredCaches() throws RemoteException {
         when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
 
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
+                mNetworkScoreCache, CACHE_FILTER_NONE);
         mNetworkScoreService.registerNetworkScoreCache(
-                NetworkKey.TYPE_WIFI, mNetworkScoreCache2);
+                NetworkKey.TYPE_WIFI, mNetworkScoreCache2, CACHE_FILTER_NONE);
 
         // updateScores should update both caches
         mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
@@ -215,6 +221,9 @@
         // updateScores should not update any caches since they are both unregistered
         mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
 
+        // The register and unregister calls grab the binder from the score cache.
+        verify(mNetworkScoreCache, atLeastOnce()).asBinder();
+        verify(mNetworkScoreCache2, atLeastOnce()).asBinder();
         verifyNoMoreInteractions(mNetworkScoreCache, mNetworkScoreCache2);
     }
 
@@ -244,7 +253,8 @@
     public void testClearScores_activeScorer() throws RemoteException {
         when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
 
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
+                CACHE_FILTER_NONE);
         mNetworkScoreService.clearScores();
 
         verify(mNetworkScoreCache).clearScores();
@@ -257,7 +267,8 @@
         when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
 
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
+                CACHE_FILTER_NONE);
         mNetworkScoreService.clearScores();
 
         verify(mNetworkScoreCache).clearScores();
@@ -280,7 +291,8 @@
     public void testSetActiveScorer_failure() throws RemoteException {
         when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER);
         when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(false);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
+                CACHE_FILTER_NONE);
 
         boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
 
@@ -297,7 +309,8 @@
     public void testSetActiveScorer_success() throws RemoteException {
         when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, NEW_SCORER);
         when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(true);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
+                CACHE_FILTER_NONE);
 
         boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
 
@@ -333,7 +346,8 @@
         when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
         when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
         when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
+                CACHE_FILTER_NONE);
 
         mNetworkScoreService.disableScoring();
 
@@ -354,7 +368,8 @@
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
         when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
+                CACHE_FILTER_NONE);
 
         mNetworkScoreService.disableScoring();
 
@@ -374,7 +389,7 @@
 
         try {
             mNetworkScoreService.registerNetworkScoreCache(
-                NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+                NetworkKey.TYPE_WIFI, mNetworkScoreCache, CACHE_FILTER_NONE);
             fail("SecurityException expected");
         } catch (SecurityException e) {
             // expected
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
index 21560ac..5eee7b9 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
@@ -336,7 +337,7 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -344,7 +345,7 @@
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -374,7 +375,7 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -382,7 +383,7 @@
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -412,7 +413,7 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -420,7 +421,7 @@
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -450,16 +451,17 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                        BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+                        ROAMING_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+                        ROAMING_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES,
+                        2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
index 4b69eb3..728eb73 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
@@ -22,6 +22,9 @@
 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.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
 import static android.net.NetworkStats.ROAMING_ALL;
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.ROAMING_YES;
@@ -40,21 +43,21 @@
 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 org.easymock.EasyMock.anyInt;
-import static org.easymock.EasyMock.anyLong;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.isA;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
 
 import android.app.AlarmManager;
-import android.app.IAlarmListener;
-import android.app.IAlarmManager;
-import android.app.PendingIntent;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
 import android.content.Intent;
@@ -63,6 +66,7 @@
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkStatsSession;
 import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkState;
@@ -80,11 +84,10 @@
 import android.os.MessageQueue.IdleHandler;
 import android.os.Message;
 import android.os.PowerManager;
-import android.os.WorkSource;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.util.TrustedTime;
 
 import com.android.internal.net.VpnInfo;
@@ -95,8 +98,14 @@
 
 import libcore.io.IoUtils;
 
-import org.easymock.Capture;
-import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -106,11 +115,11 @@
 /**
  * Tests for {@link NetworkStatsService}.
  *
- * TODO: This test is really brittle, largely due to overly-strict use of Easymock.
- * Rewrite w/ Mockito.
+ * TODO: This test used to be really brittle because it used Easymock - it uses Mockito now, but
+ * still uses the Easymock structure, which could be simplified.
  */
-@LargeTest
-public class NetworkStatsServiceTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class NetworkStatsServiceTest {
     private static final String TAG = "NetworkStatsServiceTest";
 
     private static final String TEST_IFACE = "test0";
@@ -137,10 +146,12 @@
     private BroadcastInterceptingContext mServiceContext;
     private File mStatsDir;
 
-    private INetworkManagementService mNetManager;
-    private TrustedTime mTime;
-    private NetworkStatsSettings mSettings;
-    private IConnectivityManager mConnManager;
+    private @Mock INetworkManagementService mNetManager;
+    private @Mock TrustedTime mTime;
+    private @Mock NetworkStatsSettings mSettings;
+    private @Mock IConnectivityManager mConnManager;
+    private @Mock IBinder mBinder;
+    private @Mock AlarmManager mAlarmManager;
     private IdleableHandlerThread mHandlerThread;
     private Handler mHandler;
 
@@ -148,32 +159,24 @@
     private INetworkStatsSession mSession;
     private INetworkManagementEventObserver mNetworkObserver;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        final Context context = InstrumentationRegistry.getContext();
 
-        mServiceContext = new BroadcastInterceptingContext(getContext());
-        mStatsDir = getContext().getFilesDir();
+        mServiceContext = new BroadcastInterceptingContext(context);
+        mStatsDir = context.getFilesDir();
         if (mStatsDir.exists()) {
             IoUtils.deleteContents(mStatsDir);
         }
 
-        mNetManager = createMock(INetworkManagementService.class);
-
-        // TODO: Mock AlarmManager when migrating this test to Mockito.
-        AlarmManager alarmManager = (AlarmManager) mServiceContext
-                .getSystemService(Context.ALARM_SERVICE);
-        mTime = createMock(TrustedTime.class);
-        mSettings = createMock(NetworkStatsSettings.class);
-        mConnManager = createMock(IConnectivityManager.class);
-
         PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
                 Context.POWER_SERVICE);
         PowerManager.WakeLock wakeLock =
                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
         mService = new NetworkStatsService(
-                mServiceContext, mNetManager, alarmManager, wakeLock, mTime,
+                mServiceContext, mNetManager, mAlarmManager, wakeLock, mTime,
                 TelephonyManager.getDefault(), mSettings, new NetworkStatsObservers(),
                 mStatsDir, getBaseDir(mStatsDir));
         mHandlerThread = new IdleableHandlerThread("HandlerThread");
@@ -190,22 +193,20 @@
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
-        // catch INetworkManagementEventObserver during systemReady()
-        final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
-                INetworkManagementEventObserver>();
-        mNetManager.registerObserver(capture(networkObserver));
-        expectLastCall().atLeastOnce();
-
-        replay();
         mService.systemReady();
         mSession = mService.openSession();
-        verifyAndReset();
+        assertNotNull("openSession() failed", mSession);
 
+
+        // catch INetworkManagementEventObserver during systemReady()
+        ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
+              ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
+        verify(mNetManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
 
     }
 
-    @Override
+    @After
     public void tearDown() throws Exception {
         IoUtils.deleteContents(mStatsDir);
 
@@ -219,10 +220,9 @@
 
         mSession.close();
         mService = null;
-
-        super.tearDown();
     }
 
+    @Test
     public void testNetworkStatsWifi() throws Exception {
         // pretend that wifi network comes online; service should ask about full
         // network state, and poll any existing interfaces before updating.
@@ -231,15 +231,13 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
-        verifyAndReset();
+
 
         // modify some number on wifi, and trigger poll event
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -248,14 +246,11 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
-        verifyAndReset();
+
 
         // and bump forward again, with counters going higher. this is
         // important, since polling should correctly subtract last snapshot.
@@ -265,17 +260,14 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 4096L, 4L, 8192L, 8L));
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
-        verifyAndReset();
 
     }
 
+    @Test
     public void testStatsRebootPersist() throws Exception {
         assertStatsFilesExist(false);
 
@@ -286,15 +278,13 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
-        verifyAndReset();
+
 
         // modify some number on wifi, and trigger poll event
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -308,33 +298,28 @@
                 .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
-        expectNetworkStatsPoll();
-
         mService.setUidForeground(UID_RED, false);
         mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
         mService.setUidForeground(UID_RED, true);
         mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
         assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
-        assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, ROAMING_NO, 512L, 4L, 256L, 2L, 4);
-        assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, ROAMING_NO, 512L, 4L, 256L, 2L,
-                6);
+        assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, METERED_NO, ROAMING_NO, 512L, 4L, 256L,
+                2L, 4);
+        assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, METERED_NO, ROAMING_NO, 512L, 4L,
+                256L, 2L, 6);
         assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
-        verifyAndReset();
+
 
         // graceful shutdown system, which should trigger persist of stats, and
         // clear any values in memory.
         expectCurrentTime();
         expectDefaultSettings();
-        replay();
         mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
-        verifyAndReset();
-
         assertStatsFilesExist(true);
 
         // boot through serviceReady() again
@@ -343,30 +328,22 @@
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
-        // catch INetworkManagementEventObserver during systemReady()
-        final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
-                INetworkManagementEventObserver>();
-        mNetManager.registerObserver(capture(networkObserver));
-        expectLastCall().atLeastOnce();
-
-        replay();
         mService.systemReady();
 
-        mNetworkObserver = networkObserver.getValue();
-
         // after systemReady(), we should have historical stats loaded again
         assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
         assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
-        assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, ROAMING_NO, 512L, 4L, 256L, 2L, 4);
-        assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, ROAMING_NO, 512L, 4L, 256L, 2L,
-                6);
+        assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, METERED_NO, ROAMING_NO, 512L, 4L, 256L,
+                2L, 4);
+        assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, METERED_NO, ROAMING_NO, 512L, 4L,
+                256L, 2L, 6);
         assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
-        verifyAndReset();
 
     }
 
     // TODO: simulate reboot to test bucket resize
-    @Suppress
+    @Test
+    @Ignore
     public void testStatsBucketResize() throws Exception {
         NetworkStatsHistory history = null;
 
@@ -379,12 +356,10 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // modify some number on wifi, and trigger poll event
         incrementCurrentTime(2 * HOUR_IN_MILLIS);
@@ -393,9 +368,6 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 512L, 4L, 512L, 4L));
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
@@ -403,7 +375,7 @@
         assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
         assertEquals(HOUR_IN_MILLIS, history.getBucketDuration());
         assertEquals(2, history.size());
-        verifyAndReset();
+
 
         // now change bucket duration setting and trigger another poll with
         // exact same values, which should resize existing buckets.
@@ -411,9 +383,6 @@
         expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify identical stats, but spread across 4 buckets now
@@ -421,10 +390,10 @@
         assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
         assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration());
         assertEquals(4, history.size());
-        verifyAndReset();
 
     }
 
+    @Test
     public void testUidStatsAcrossNetworks() throws Exception {
         // pretend first mobile network comes online
         expectCurrentTime();
@@ -432,12 +401,10 @@
         expectNetworkState(buildMobile3gState(IMSI_1));
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some traffic on first network
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -449,11 +416,8 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xF00D, 10);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
@@ -461,7 +425,7 @@
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
         assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10);
         assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0);
-        verifyAndReset();
+
 
         // now switch networks; this also tests that we're okay with interfaces
         // disappearing, to verify we don't count backwards.
@@ -475,13 +439,11 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
         forcePollAndWaitForIdle();
-        verifyAndReset();
+
 
         // create traffic on second network
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -494,11 +456,8 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify original history still intact
@@ -511,10 +470,10 @@
         assertNetworkTotal(sTemplateImsi2, 128L, 1L, 1024L, 8L, 0);
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
         assertUidTotal(sTemplateImsi2, UID_BLUE, 128L, 1L, 1024L, 8L, 10);
-        verifyAndReset();
 
     }
 
+    @Test
     public void testUidRemovedIsMoved() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -522,12 +481,10 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -540,11 +497,8 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
                 .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
@@ -552,7 +506,7 @@
         assertUidTotal(sTemplateWifi, UID_RED, 16L, 1L, 16L, 1L, 10);
         assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 258L, 512L, 32L, 0);
         assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
-        verifyAndReset();
+
 
         // now pretend two UIDs are uninstalled, which should migrate stats to
         // special "removed" bucket.
@@ -565,9 +519,6 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
                 .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
-        expectNetworkStatsPoll();
-
-        replay();
         final Intent intent = new Intent(ACTION_UID_REMOVED);
         intent.putExtra(EXTRA_UID, UID_BLUE);
         mServiceContext.sendBroadcast(intent);
@@ -581,10 +532,10 @@
         assertUidTotal(sTemplateWifi, UID_BLUE, 0L, 0L, 0L, 0L, 0);
         assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
         assertUidTotal(sTemplateWifi, UID_REMOVED, 4112L, 259L, 528L, 33L, 10);
-        verifyAndReset();
 
     }
 
+    @Test
     public void testUid3g4gCombinedByTemplate() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -592,12 +543,10 @@
         expectNetworkState(buildMobile3gState(IMSI_1));
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -607,16 +556,13 @@
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xF00D, 5);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5);
-        verifyAndReset();
+
 
         // now switch over to 4g network
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -627,13 +573,11 @@
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
         forcePollAndWaitForIdle();
-        verifyAndReset();
+
 
         // create traffic on second network
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -645,19 +589,15 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
                 .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify that ALL_MOBILE template combines both
         assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
-
-        verifyAndReset();
     }
 
+    @Test
     public void testSummaryForAllUid() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -665,12 +605,10 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some traffic for two apps
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -681,17 +619,14 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateWifi, UID_RED, 50L, 5L, 50L, 5L, 1);
         assertUidTotal(sTemplateWifi, UID_BLUE, 1024L, 8L, 512L, 4L, 0);
-        verifyAndReset();
+
 
         // now create more traffic in next hour, but only for one app
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -702,33 +637,29 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // first verify entire history present
         NetworkStats stats = mSession.getSummaryForAllUid(
                 sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
         assertEquals(3, stats.size());
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO, 50L, 5L,
-                50L, 5L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_NO, 10L, 1L, 10L,
-                1L, 1);
-        assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, ROAMING_NO, 2048L, 16L,
-                1024L, 8L, 0);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 50L,
+                5L, 50L, 5L, 1);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 10L,
+                1L, 10L, 1L, 1);
+        assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                2048L, 16L, 1024L, 8L, 0);
 
         // now verify that recent history only contains one uid
         final long currentTime = currentTimeMillis();
         stats = mSession.getSummaryForAllUid(
                 sTemplateWifi, currentTime - HOUR_IN_MILLIS, currentTime, true);
         assertEquals(1, stats.size());
-        assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                512L, 4L, 0);
-
-        verifyAndReset();
+        assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                1024L, 8L, 512L, 4L, 0);
     }
 
+    @Test
     public void testForegroundBackground() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -736,12 +667,10 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some initial traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -751,16 +680,13 @@
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
-        verifyAndReset();
+
 
         // now switch to foreground
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -772,12 +698,9 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
-        expectNetworkStatsPoll();
-
         mService.setUidForeground(UID_RED, true);
         mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // test that we combined correctly
@@ -787,18 +710,59 @@
         final NetworkStats stats = mSession.getSummaryForAllUid(
                 sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
         assertEquals(4, stats.size());
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 2L,
-                128L, 2L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_NO, 64L, 1L, 64L,
-                1L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 32L, 2L,
-                32L, 2L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, 0xFAAD, ROAMING_NO, 1L, 1L, 1L,
-                1L, 1);
-
-        verifyAndReset();
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L,
+                2L, 128L, 2L, 1);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L,
+                1L, 64L, 1L, 1);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                32L, 2L, 32L, 2L, 1);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, 0xFAAD, METERED_NO, ROAMING_NO, 1L,
+                1L, 1L, 1L, 1);
     }
 
+    @Test
+    public void testMetered() throws Exception {
+        // pretend that network comes online
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkState(buildWifiState(true /* isMetered */));
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectBandwidthControlCheck();
+
+        mService.forceUpdateIfaces();
+
+
+        // create some initial traffic
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats());
+        // Note that all traffic from NetworkManagementService is tagged as METERED_NO and
+        // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
+        // on top by inspecting the iface properties.
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L,
+                        2L, 128L, 2L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L,
+                        1L, 64L, 1L, 0L));
+        mService.incrementOperationCount(UID_RED, 0xF00D, 1);
+
+        forcePollAndWaitForIdle();
+
+        // verify service recorded history
+        assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+        // verify entire history present
+        final NetworkStats stats = mSession.getSummaryForAllUid(
+                sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+        assertEquals(2, stats.size());
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                128L, 2L, 128L, 2L, 1);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, 64L,
+                1L, 64L, 1L, 1);
+    }
+
+    @Test
     public void testRoaming() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -806,29 +770,24 @@
         expectNetworkState(buildMobile3gState(IMSI_1, true /* isRoaming */));
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // Create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
         expectCurrentTime();
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
-        // Note that all traffic from NetworkManagementService is tagged as ROAMING_NO, because
-        // roaming isn't tracked at that layer. We layer it on top by inspecting the iface
-        // properties.
+        // Note that all traffic from NetworkManagementService is tagged as METERED_NO and
+        // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
+        // on top by inspecting the iface properties.
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 2L,
-                        128L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_NO, 64L, 1L, 64L,
-                        1L, 0L));
-        expectNetworkStatsPoll();
-
-        replay();
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
+                        128L, 2L, 128L, 2L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO, 64L,
+                        1L, 64L, 1L, 0L));
         forcePollAndWaitForIdle();
 
         // verify service recorded history
@@ -838,14 +797,13 @@
         final NetworkStats stats = mSession.getSummaryForAllUid(
                 sTemplateImsi1, Long.MIN_VALUE, Long.MAX_VALUE, true);
         assertEquals(2, stats.size());
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_YES, 128L, 2L,
-                128L, 2L, 0);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_YES, 64L, 1L, 64L,
-                1L, 0);
-
-        verifyAndReset();
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_YES,
+                128L, 2L, 128L, 2L, 0);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_YES, 64L,
+                1L, 64L, 1L, 0);
     }
 
+    @Test
     public void testTethering() throws Exception {
         // pretend first mobile network comes online
         expectCurrentTime();
@@ -853,12 +811,10 @@
         expectNetworkState(buildMobile3gState(IMSI_1));
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some tethering traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -871,22 +827,20 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
         final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" };
         final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L);
+                .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
+                        0L);
 
         expectNetworkStatsUidDetail(uidStats, tetherIfacePairs, tetherStats);
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
         assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
         assertUidTotal(sTemplateImsi1, UID_TETHERING, 1920L, 14L, 384L, 2L, 0);
-        verifyAndReset();
 
     }
 
+    @Test
     public void testRegisterUsageCallback() throws Exception {
         // pretend that wifi network comes online; service should ask about full
         // network state, and poll any existing interfaces before updating.
@@ -895,16 +849,12 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
-        verifyAndReset();
-
         String callingPackage = "the.calling.package";
         long thresholdInBytes = 1L;  // very small; should be overriden by framework
         DataUsageRequest inputRequest = new DataUsageRequest(
@@ -915,23 +865,18 @@
         LatchedHandler latchedHandler = new LatchedHandler(Looper.getMainLooper(), cv);
         Messenger messenger = new Messenger(latchedHandler);
 
-        // Allow binder to connect
-        IBinder mockBinder = createMock(IBinder.class);
-        mockBinder.linkToDeath((IBinder.DeathRecipient) anyObject(), anyInt());
-        EasyMock.replay(mockBinder);
-
         // Force poll
         expectCurrentTime();
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-        replay();
+
+
 
         // Register and verify request and that binder was called
         DataUsageRequest request =
                 mService.registerUsageCallback(callingPackage, inputRequest,
-                        messenger, mockBinder);
+                        messenger, mBinder);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateWifi, request.template));
         long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
@@ -941,11 +886,11 @@
         mHandler.sendMessage(mHandler.obtainMessage(-1));
         mHandlerThread.waitForIdle(WAIT_TIMEOUT);
 
-        verifyAndReset();
+
 
         // Make sure that the caller binder gets connected
-        EasyMock.verify(mockBinder);
-        EasyMock.reset(mockBinder);
+        verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+
 
         // modify some number on wifi, and trigger poll event
         // not enough traffic to call data usage callback
@@ -955,13 +900,9 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
-        verifyAndReset();
         assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
 
         // make sure callback has not being called
@@ -975,14 +916,11 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 4096000L, 4L, 8192000L, 8L));
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 4096000L, 4L, 8192000L, 8L, 0);
-        verifyAndReset();
+
 
         // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
         assertTrue(cv.block(WAIT_TIMEOUT));
@@ -990,9 +928,7 @@
         cv.close();
 
         // Allow binder to disconnect
-        expect(mockBinder.unlinkToDeath((IBinder.DeathRecipient) anyObject(), anyInt()))
-                .andReturn(true);
-        EasyMock.replay(mockBinder);
+        when(mBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt())).thenReturn(true);
 
         // Unregister request
         mService.unregisterUsageRequest(request);
@@ -1002,9 +938,10 @@
         assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.mLastMessageType);
 
         // Make sure that the caller binder gets disconnected
-        EasyMock.verify(mockBinder);
+        verify(mBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
     }
 
+    @Test
     public void testUnregisterUsageCallback_unknown_noop() throws Exception {
         String callingPackage = "the.calling.package";
         long thresholdInBytes = 10 * 1024 * 1024;  // 10 MB
@@ -1034,18 +971,18 @@
 
         // verify summary API
         final NetworkStats stats = mSession.getSummaryForNetwork(template, start, end);
-        assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, ROAMING_NO, rxBytes,
-                rxPackets, txBytes, txPackets, operations);
+        assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
+                rxBytes, rxPackets, txBytes, txPackets, operations);
     }
 
     private void assertUidTotal(NetworkTemplate template, int uid, long rxBytes, long rxPackets,
             long txBytes, long txPackets, int operations) throws Exception {
-        assertUidTotal(template, uid, SET_ALL, ROAMING_ALL, rxBytes, rxPackets, txBytes, txPackets,
-                operations);
+        assertUidTotal(template, uid, SET_ALL, METERED_ALL, ROAMING_ALL, rxBytes, rxPackets,
+                txBytes, txPackets, operations);
     }
 
-    private void assertUidTotal(NetworkTemplate template, int uid, int set, int roaming,
-            long rxBytes, long rxPackets, long txBytes, long txPackets, int operations)
+    private void assertUidTotal(NetworkTemplate template, int uid, int set, int metered,
+            int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations)
             throws Exception {
         // verify history API
         final NetworkStatsHistory history = mSession.getHistoryForUid(
@@ -1056,38 +993,35 @@
         // verify summary API
         final NetworkStats stats = mSession.getSummaryForAllUid(
                 template, Long.MIN_VALUE, Long.MAX_VALUE, false);
-        assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, roaming, rxBytes, rxPackets, txBytes,
-                txPackets, operations);
+        assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, metered, roaming, rxBytes, rxPackets,
+                txBytes, txPackets, operations);
     }
 
     private void expectSystemReady() throws Exception {
-        mNetManager.setGlobalAlert(anyLong());
-        expectLastCall().atLeastOnce();
-
         expectNetworkStatsSummary(buildEmptyStats());
         expectBandwidthControlCheck();
     }
 
     private void expectNetworkState(NetworkState... state) throws Exception {
-        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+        when(mConnManager.getAllNetworkState()).thenReturn(state);
 
         final LinkProperties linkProp = state.length > 0 ? state[0].linkProperties : null;
-        expect(mConnManager.getActiveLinkProperties()).andReturn(linkProp).atLeastOnce();
+        when(mConnManager.getActiveLinkProperties()).thenReturn(linkProp);
     }
 
     private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
-        expect(mConnManager.getAllVpnInfo()).andReturn(new VpnInfo[0]).atLeastOnce();
+        when(mConnManager.getAllVpnInfo()).thenReturn(new VpnInfo[0]);
 
         expectNetworkStatsSummaryDev(summary);
         expectNetworkStatsSummaryXt(summary);
     }
 
     private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
-        expect(mNetManager.getNetworkStatsSummaryDev()).andReturn(summary).atLeastOnce();
+        when(mNetManager.getNetworkStatsSummaryDev()).thenReturn(summary);
     }
 
     private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
-        expect(mNetManager.getNetworkStatsSummaryXt()).andReturn(summary).atLeastOnce();
+        when(mNetManager.getNetworkStatsSummaryXt()).thenReturn(summary);
     }
 
     private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
@@ -1097,11 +1031,10 @@
     private void expectNetworkStatsUidDetail(
             NetworkStats detail, String[] tetherIfacePairs, NetworkStats tetherStats)
             throws Exception {
-        expect(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL))).andReturn(detail).atLeastOnce();
+        when(mNetManager.getNetworkStatsUidDetail(UID_ALL)).thenReturn(detail);
 
         // also include tethering details, since they are folded into UID
-        expect(mNetManager.getNetworkStatsTethering())
-                .andReturn(tetherStats).atLeastOnce();
+        when(mNetManager.getNetworkStatsTethering()).thenReturn(tetherStats);
     }
 
     private void expectDefaultSettings() throws Exception {
@@ -1110,38 +1043,33 @@
 
     private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
             throws Exception {
-        expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
-        expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
-        expect(mSettings.getSampleEnabled()).andReturn(true).anyTimes();
+        when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
+        when(mSettings.getTimeCacheMaxAge()).thenReturn(DAY_IN_MILLIS);
+        when(mSettings.getSampleEnabled()).thenReturn(true);
 
         final Config config = new Config(bucketDuration, deleteAge, deleteAge);
-        expect(mSettings.getDevConfig()).andReturn(config).anyTimes();
-        expect(mSettings.getXtConfig()).andReturn(config).anyTimes();
-        expect(mSettings.getUidConfig()).andReturn(config).anyTimes();
-        expect(mSettings.getUidTagConfig()).andReturn(config).anyTimes();
+        when(mSettings.getDevConfig()).thenReturn(config);
+        when(mSettings.getXtConfig()).thenReturn(config);
+        when(mSettings.getUidConfig()).thenReturn(config);
+        when(mSettings.getUidTagConfig()).thenReturn(config);
 
-        expect(mSettings.getGlobalAlertBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
-        expect(mSettings.getDevPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
-        expect(mSettings.getXtPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
-        expect(mSettings.getUidPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
-        expect(mSettings.getUidTagPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
+        when(mSettings.getGlobalAlertBytes(anyLong())).thenReturn(MB_IN_BYTES);
+        when(mSettings.getDevPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+        when(mSettings.getXtPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+        when(mSettings.getUidPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+        when(mSettings.getUidTagPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
     }
 
     private void expectCurrentTime() throws Exception {
-        expect(mTime.forceRefresh()).andReturn(false).anyTimes();
-        expect(mTime.hasCache()).andReturn(true).anyTimes();
-        expect(mTime.currentTimeMillis()).andReturn(currentTimeMillis()).anyTimes();
-        expect(mTime.getCacheAge()).andReturn(0L).anyTimes();
-        expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
-    }
-
-    private void expectNetworkStatsPoll() throws Exception {
-        mNetManager.setGlobalAlert(anyLong());
-        expectLastCall().anyTimes();
+        when(mTime.forceRefresh()).thenReturn(false);
+        when(mTime.hasCache()).thenReturn(true);
+        when(mTime.currentTimeMillis()).thenReturn(currentTimeMillis());
+        when(mTime.getCacheAge()).thenReturn(0L);
+        when(mTime.getCacheCertainty()).thenReturn(0L);
     }
 
     private void expectBandwidthControlCheck() throws Exception {
-        expect(mNetManager.isBandwidthControlEnabled()).andReturn(true).atLeastOnce();
+        when(mNetManager.isBandwidthControlEnabled()).thenReturn(true);
     }
 
     private void assertStatsFilesExist(boolean exist) {
@@ -1154,8 +1082,8 @@
     }
 
     private static void assertValues(NetworkStats stats, String iface, int uid, int set,
-            int tag, int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets,
-            int operations) {
+            int tag, int metered, int roaming, long rxBytes, long rxPackets, long txBytes,
+            long txPackets, int operations) {
         final NetworkStats.Entry entry = new NetworkStats.Entry();
         List<Integer> sets = new ArrayList<>();
         if (set == SET_DEFAULT || set == SET_ALL) {
@@ -1173,11 +1101,21 @@
             roamings.add(ROAMING_YES);
         }
 
+        List<Integer> meterings = new ArrayList<>();
+        if (metered == METERED_NO || metered == METERED_ALL) {
+            meterings.add(METERED_NO);
+        }
+        if (metered == METERED_YES || metered == METERED_ALL) {
+            meterings.add(METERED_YES);
+        }
+
         for (int s : sets) {
             for (int r : roamings) {
-                final int i = stats.findIndex(iface, uid, s, tag, r);
-                if (i != -1) {
-                    entry.add(stats.getValues(i, null));
+                for (int m : meterings) {
+                    final int i = stats.findIndex(iface, uid, s, tag, m, r);
+                    if (i != -1) {
+                        entry.add(stats.getValues(i, null));
+                    }
                 }
             }
         }
@@ -1200,11 +1138,19 @@
     }
 
     private static NetworkState buildWifiState() {
+        return buildWifiState(false);
+    }
+
+    private static NetworkState buildWifiState(boolean isMetered) {
         final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
         info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
-        return new NetworkState(info, prop, null, null, null, TEST_SSID);
+        final NetworkCapabilities capabilities = new NetworkCapabilities();
+        if (!isMetered) {
+            capabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        }
+        return new NetworkState(info, prop, capabilities, null, null, TEST_SSID);
     }
 
     private static NetworkState buildMobile3gState(String subscriberId) {
@@ -1218,7 +1164,8 @@
         info.setRoaming(isRoaming);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
-        return new NetworkState(info, prop, null, null, subscriberId, null);
+        final NetworkCapabilities capabilities = new NetworkCapabilities();
+        return new NetworkState(info, prop, capabilities, null, subscriberId, null);
     }
 
     private static NetworkState buildMobile4gState(String iface) {
@@ -1226,7 +1173,8 @@
         info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(iface);
-        return new NetworkState(info, prop, null, null, null, null);
+        final NetworkCapabilities capabilities = new NetworkCapabilities();
+        return new NetworkState(info, prop, capabilities, null, null, null);
     }
 
     private NetworkStats buildEmptyStats() {
@@ -1249,15 +1197,6 @@
         mElapsedRealtime += duration;
     }
 
-    private void replay() {
-        EasyMock.replay(mNetManager, mTime, mSettings, mConnManager);
-    }
-
-    private void verifyAndReset() {
-        EasyMock.verify(mNetManager, mTime, mSettings, mConnManager);
-        EasyMock.reset(mNetManager, mTime, mSettings, mConnManager);
-    }
-
     private void forcePollAndWaitForIdle() {
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
         // Send dummy message to make sure that any previous message has been handled
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 15a1e76..b3f5630 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -351,8 +351,6 @@
                             UsbManager.removeFunction(persisted, UsbManager.USB_FUNCTION_MTP));
                 }
 
-                setEnabledFunctions(null, false, false);
-
                 String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
                 updateState(state);
 
@@ -446,13 +444,12 @@
             return false;
         }
 
-        private boolean setUsbConfig(String config) {
+        private void setUsbConfig(String config) {
             if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")");
             // set the new configuration
             // we always set it due to b/23631400, where adbd was getting killed
             // and not restarted due to property timeouts on some devices
             SystemProperties.set(USB_CONFIG_PROPERTY, config);
-            return waitForState(config);
         }
 
         private void setAdbEnabled(boolean enable) {
@@ -547,8 +544,18 @@
                 // Kick the USB stack to close existing connections.
                 setUsbConfig(UsbManager.USB_FUNCTION_NONE);
 
+                if (!waitForState(UsbManager.USB_FUNCTION_NONE)) {
+                    Slog.e(TAG, "Failed to kick USB config");
+                    return false;
+                }
+
                 // Set the new USB configuration.
-                if (!setUsbConfig(functions)) {
+                setUsbConfig(functions);
+
+                // Start up dependent services.
+                updateUsbStateBroadcastIfNeeded(true);
+
+                if (!waitForState(functions)) {
                     Slog.e(TAG, "Failed to switch USB config to " + functions);
                     return false;
                 }
@@ -631,7 +638,7 @@
             return false;
         }
 
-        private void updateUsbStateBroadcastIfNeeded() {
+        private void updateUsbStateBroadcastIfNeeded(boolean configChanged) {
             // send a sticky broadcast containing current USB state
             Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
             intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
@@ -640,6 +647,7 @@
             intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected);
             intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured);
             intent.putExtra(UsbManager.USB_DATA_UNLOCKED, isUsbTransferAllowed() && mUsbDataUnlocked);
+            intent.putExtra(UsbManager.USB_CONFIG_CHANGED, configChanged);
 
             if (mCurrentFunctions != null) {
                 String[] functions = mCurrentFunctions.split(",");
@@ -737,7 +745,7 @@
                         setEnabledFunctions(null, false, false);
                     }
                     if (mBootCompleted) {
-                        updateUsbStateBroadcastIfNeeded();
+                        updateUsbStateBroadcastIfNeeded(false);
                         updateUsbFunctions();
                     }
                     break;
@@ -749,7 +757,7 @@
                     args.recycle();
                     updateUsbNotification();
                     if (mBootCompleted) {
-                        updateUsbStateBroadcastIfNeeded();
+                        updateUsbStateBroadcastIfNeeded(false);
                     }
                     break;
                 case MSG_ENABLE_ADB:
@@ -765,11 +773,11 @@
                 case MSG_SYSTEM_READY:
                     updateUsbNotification();
                     updateAdbNotification();
-                    updateUsbStateBroadcastIfNeeded();
                     updateUsbFunctions();
                     break;
                 case MSG_BOOT_COMPLETED:
                     mBootCompleted = true;
+                    setEnabledFunctions(null, false, false);
                     if (mCurrentAccessory != null) {
                         getCurrentSettings().accessoryAttached(mCurrentAccessory);
                     }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 40564b6..8225110 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -143,6 +143,14 @@
     public static final String
             KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
 
+    /**
+     * Control whether users receive a simplified network settings UI and improved network
+     * selection.
+     * @hide
+     */
+    public static final String
+            KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
+
     /** Control whether users can reach the SIM lock settings. */
     public static final String
             KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
@@ -1072,6 +1080,7 @@
         sDefaults.putBoolean(KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL, true);
         sDefaults.putBoolean(KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL, false);
         sDefaults.putBoolean(KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        sDefaults.putBoolean(KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL, false);
         sDefaults.putBoolean(KEY_HIDE_SIM_LOCK_SETTINGS_BOOL, false);
         sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false);
         sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
diff --git a/tests/net/java/android/net/ConnectivityMetricsLoggerTest.java b/tests/net/java/android/net/ConnectivityMetricsLoggerTest.java
index 6d42cce..f896030 100644
--- a/tests/net/java/android/net/ConnectivityMetricsLoggerTest.java
+++ b/tests/net/java/android/net/ConnectivityMetricsLoggerTest.java
@@ -18,6 +18,7 @@
 
 import android.os.Bundle;
 import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
 import java.util.List;
 import junit.framework.TestCase;
 import org.mockito.ArgumentCaptor;
@@ -49,6 +50,7 @@
         mLog = new ConnectivityMetricsLogger(mService);
     }
 
+    @SmallTest
     public void testLogEvents() throws Exception {
         mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
         mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
@@ -60,6 +62,7 @@
         assertEventsEqual(expectedEvent(3), gotEvents.get(2));
     }
 
+    @SmallTest
     public void testLogEventTriggerThrottling() throws Exception {
         when(mService.logEvent(any())).thenReturn(1234L);
 
@@ -70,6 +73,7 @@
         assertEventsEqual(expectedEvent(1), gotEvents.get(0));
     }
 
+    @SmallTest
     public void testLogEventFails() throws Exception {
         when(mService.logEvent(any())).thenReturn(-1L); // Error.
 
@@ -80,6 +84,7 @@
         assertEventsEqual(expectedEvent(1), gotEvents.get(0));
     }
 
+    @SmallTest
     public void testLogEventWhenThrottling() throws Exception {
         when(mService.logEvent(any())).thenReturn(Long.MAX_VALUE); // Throttled
 
@@ -92,6 +97,7 @@
         assertEventsEqual(expectedEvent(1), gotEvents.get(0));
     }
 
+    @SmallTest
     public void testLogEventRecoverFromThrottling() throws Exception {
         final long throttleTimeout = System.currentTimeMillis() + 10;
         when(mService.logEvent(any())).thenReturn(throttleTimeout, 0L);
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 81f66a5..ff61754 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -33,7 +33,6 @@
 import android.system.Os;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.MediumTest;
 import static android.system.OsConstants.*;
 
 import com.android.frameworks.tests.net.R;
@@ -155,7 +154,7 @@
      * generating bytecode for that program and running it through the
      * interpreter to verify it functions correctly.
      */
-    @MediumTest
+    @SmallTest
     public void testApfInstructions() throws IllegalInstructionException {
         // Empty program should pass because having the program counter reach the
         // location immediately after the program indicates the packet should be
@@ -563,7 +562,7 @@
      * Generate some BPF programs, translate them to APF, then run APF and BPF programs
      * over packet traces and verify both programs filter out the same packets.
      */
-    @MediumTest
+    @SmallTest
     public void testApfAgainstBpf() throws Exception {
         String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
                 "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
@@ -662,9 +661,13 @@
     // The IPv6 all nodes address ff02::1
     private static final byte[] IPV6_ALL_NODES_ADDRESS =
             { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+    private static final byte[] IPV6_ALL_ROUTERS_ADDRESS =
+            { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
 
     private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+    private static final int ICMP6_ROUTER_SOLICITATION = 133;
     private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
+    private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
     private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
 
     private static final int ICMP6_RA_HEADER_LEN = 16;
@@ -721,7 +724,7 @@
     private static final byte[] ANOTHER_IPV4_ADDR        = {10, 0, 0, 2};
     private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
 
-    @MediumTest
+    @SmallTest
     public void testApfFilterIPv4() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
@@ -776,7 +779,7 @@
         apfFilter.shutdown();
     }
 
-    @MediumTest
+    @SmallTest
     public void testApfFilterIPv6() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
@@ -799,10 +802,16 @@
         put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
         assertDrop(program, packet.array());
 
+        // Verify ICMPv6 RS to any is dropped
+        packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION);
+        assertDrop(program, packet.array());
+        put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
+        assertDrop(program, packet.array());
+
         apfFilter.shutdown();
     }
 
-    @MediumTest
+    @SmallTest
     public void testApfFilterMulticast() throws Exception {
         final byte[] unicastIpv4Addr   = {(byte)192,0,2,63};
         final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
@@ -912,7 +921,7 @@
         assertDrop(program, garpReply());
     }
 
-    @MediumTest
+    @SmallTest
     public void testApfFilterArp() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
@@ -1031,7 +1040,7 @@
         ipManagerCallback.assertNoProgramUpdate();
     }
 
-    @MediumTest
+    @SmallTest
     public void testApfFilterRa() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog);
diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
index bc8baa1..d79c312 100644
--- a/tests/net/java/android/net/dhcp/DhcpPacketTest.java
+++ b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
@@ -473,6 +473,7 @@
         assertEquals(Integer.toHexString(expected), Integer.toHexString(got));
     }
 
+    @SmallTest
     public void testTruncatedOfferPackets() throws Exception {
         final byte[] packet = HexDump.hexStringToByteArray(
             // IP header.
@@ -506,6 +507,7 @@
         }
     }
 
+    @SmallTest
     public void testRandomPackets() throws Exception {
         final int maxRandomPacketSize = 512;
         final Random r = new Random();
diff --git a/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java b/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java
index e677475..5deba27 100644
--- a/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java
+++ b/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java
@@ -24,6 +24,7 @@
 import android.net.netlink.NetlinkErrorMessage;
 import android.net.netlink.NetlinkMessage;
 import android.net.netlink.StructNlMsgErr;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -53,6 +54,7 @@
     public static final byte[] NLM_ERROR_OK =
             HexEncoding.decode(NLM_ERROR_OK_HEX.toCharArray(), false);
 
+    @SmallTest
     public void testParseNlmErrorOk() {
         final ByteBuffer byteBuffer = ByteBuffer.wrap(NLM_ERROR_OK);
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
diff --git a/tests/net/java/android/net/netlink/NetlinkSocketTest.java b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
index c599fe3..78b3b70 100644
--- a/tests/net/java/android/net/netlink/NetlinkSocketTest.java
+++ b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
@@ -20,6 +20,7 @@
 import android.net.netlink.RtNetlinkNeighborMessage;
 import android.net.netlink.StructNdMsg;
 import android.net.netlink.StructNlMsgHdr;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
 import android.system.OsConstants;
@@ -33,6 +34,7 @@
 public class NetlinkSocketTest extends TestCase {
     private final String TAG = "NetlinkSocketTest";
 
+    @SmallTest
     public void testBasicWorkingGetNeighborsQuery() throws Exception {
         NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
         assertNotNull(s);
@@ -91,6 +93,7 @@
         s.close();
     }
 
+    @SmallTest
     public void testRepeatedCloseCallsAreQuiet() throws Exception {
         // Create a working NetlinkSocket.
         NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
diff --git a/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java b/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java
index 19ee000..029758e 100644
--- a/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java
+++ b/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java
@@ -21,12 +21,13 @@
 import android.net.netlink.RtNetlinkNeighborMessage;
 import android.net.netlink.StructNdMsg;
 import android.net.netlink.StructNlMsgHdr;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.system.OsConstants;
 import android.util.Log;
 import libcore.util.HexEncoding;
 
-import java.net.InetAddress;
 import java.net.Inet4Address;
+import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -135,6 +136,7 @@
     public static final byte[] RTM_GETNEIGH_RESPONSE =
             HexEncoding.decode(RTM_GETNEIGH_RESPONSE_HEX.replaceAll(" ", "").toCharArray(), false);
 
+    @SmallTest
     public void testParseRtmDelNeigh() {
         final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_DELNEIGH);
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
@@ -161,6 +163,7 @@
         assertEquals(InetAddress.parseNumericAddress("192.168.159.254"), destination);
     }
 
+    @SmallTest
     public void testParseRtmNewNeigh() {
         final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_NEWNEIGH);
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
@@ -187,6 +190,7 @@
         assertEquals(InetAddress.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), destination);
     }
 
+    @SmallTest
     public void testParseRtmGetNeighResponse() {
         final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_GETNEIGH_RESPONSE);
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
@@ -211,6 +215,7 @@
         assertEquals(14, messageCount);
     }
 
+    @SmallTest
     public void testCreateRtmNewNeighMessage() {
         final int seqNo = 2635;
         final int ifIndex = 14;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index fc8cc81..d62c30d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -70,7 +70,6 @@
 import android.test.AndroidTestCase;
 import android.test.FlakyTest;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 import android.util.LogPrinter;
@@ -91,6 +90,7 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BooleanSupplier;
 
 /**
  * Tests for {@link ConnectivityService}.
@@ -195,6 +195,7 @@
     }
 
     // Tests that IdleableHandlerThread works as expected.
+    @SmallTest
     public void testIdleableHandlerThread() {
         final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
 
@@ -219,6 +220,7 @@
         }
     }
 
+    @SmallTest
     @FlakyTest(tolerance = 3)
     public void testNotWaitingForIdleCausesRaceConditions() {
         // Bring up a network that we can use to send messages to ConnectivityService.
@@ -622,7 +624,7 @@
     }
 
     private class WrappedAvoidBadWifiTracker extends AvoidBadWifiTracker {
-        public boolean configRestrictsAvoidBadWifi;
+        public volatile boolean configRestrictsAvoidBadWifi;
 
         public WrappedAvoidBadWifiTracker(Context c, Handler h, Runnable r) {
             super(c, h, r);
@@ -839,7 +841,7 @@
         return cv;
     }
 
-    @LargeTest
+    @SmallTest
     public void testLingering() throws Exception {
         verifyNoNetwork();
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -879,7 +881,7 @@
         verifyNoNetwork();
     }
 
-    @LargeTest
+    @SmallTest
     public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception {
         // Test bringing up unvalidated WiFi
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -914,7 +916,7 @@
         verifyNoNetwork();
     }
 
-    @LargeTest
+    @SmallTest
     public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception {
         // Test bringing up unvalidated cellular.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -940,7 +942,7 @@
         verifyNoNetwork();
     }
 
-    @LargeTest
+    @SmallTest
     public void testUnlingeringDoesNotValidate() throws Exception {
         // Test bringing up unvalidated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -968,7 +970,7 @@
                 NET_CAPABILITY_VALIDATED));
     }
 
-    @LargeTest
+    @SmallTest
     public void testCellularOutscoresWeakWifi() throws Exception {
         // Test bringing up validated cellular.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -994,7 +996,7 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
-    @LargeTest
+    @SmallTest
     public void testReapingNetwork() throws Exception {
         // Test bringing up WiFi without NET_CAPABILITY_INTERNET.
         // Expect it to be torn down immediately because it satisfies no requests.
@@ -1027,7 +1029,7 @@
         waitFor(cv);
     }
 
-    @LargeTest
+    @SmallTest
     public void testCellularFallback() throws Exception {
         // Test bringing up validated cellular.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1065,7 +1067,7 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
-    @LargeTest
+    @SmallTest
     public void testWiFiFallback() throws Exception {
         // Test bringing up unvalidated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -1192,7 +1194,7 @@
         }
     }
 
-    @LargeTest
+    @SmallTest
     public void testStateChangeNetworkCallbacks() throws Exception {
         final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
@@ -1575,7 +1577,7 @@
         handlerThread.quit();
     }
 
-    @LargeTest
+    @SmallTest
     public void testNetworkFactoryRequests() throws Exception {
         tryNetworkFactoryRequests(NET_CAPABILITY_MMS);
         tryNetworkFactoryRequests(NET_CAPABILITY_SUPL);
@@ -1595,7 +1597,7 @@
         // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed.
     }
 
-    @LargeTest
+    @SmallTest
     public void testNoMutableNetworkRequests() throws Exception {
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0);
         NetworkRequest.Builder builder = new NetworkRequest.Builder();
@@ -1620,7 +1622,7 @@
         } catch (IllegalArgumentException expected) {}
     }
 
-    @LargeTest
+    @SmallTest
     public void testMMSonWiFi() throws Exception {
         // Test bringing up cellular without MMS NetworkRequest gets reaped
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1655,7 +1657,7 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
-    @LargeTest
+    @SmallTest
     public void testMMSonCell() throws Exception {
         // Test bringing up cellular without MMS
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1681,7 +1683,7 @@
         verifyActiveNetwork(TRANSPORT_CELLULAR);
     }
 
-    @LargeTest
+    @SmallTest
     public void testCaptivePortal() {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -1730,7 +1732,7 @@
         validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
     }
 
-    @LargeTest
+    @SmallTest
     public void testAvoidOrIgnoreCaptivePortals() {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -1801,7 +1803,7 @@
                 execptionCalled);
     }
 
-    @LargeTest
+    @SmallTest
     public void testRegisterDefaultNetworkCallback() throws Exception {
         final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
@@ -1862,7 +1864,7 @@
         }
     }
 
-    @LargeTest
+    @SmallTest
     public void testRequestCallbackUpdates() throws Exception {
         // File a network request for mobile.
         final TestNetworkCallback cellNetworkCallback = new TestRequestUpdateCallback();
@@ -1997,6 +1999,7 @@
 
     @SmallTest
     public void testRequestBenchmark() throws Exception {
+        // TODO: turn this unit test into a real benchmarking test.
         // Benchmarks connecting and switching performance in the presence of a large number of
         // NetworkRequests.
         // 1. File NUM_REQUESTS requests.
@@ -2010,61 +2013,80 @@
         final CountDownLatch availableLatch = new CountDownLatch(NUM_REQUESTS);
         final CountDownLatch losingLatch = new CountDownLatch(NUM_REQUESTS);
 
-        final int REGISTER_TIME_LIMIT_MS = 100;
-        long startTime = System.currentTimeMillis();
         for (int i = 0; i < NUM_REQUESTS; i++) {
             callbacks[i] = new NetworkCallback() {
                 @Override public void onAvailable(Network n) { availableLatch.countDown(); }
                 @Override public void onLosing(Network n, int t) { losingLatch.countDown(); }
             };
-            mCm.registerNetworkCallback(request, callbacks[i]);
         }
-        long timeTaken = System.currentTimeMillis() - startTime;
-        String msg = String.format("Register %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, REGISTER_TIME_LIMIT_MS);
-        Log.d(TAG, msg);
-        assertTrue(msg, timeTaken < REGISTER_TIME_LIMIT_MS);
 
-        final int CONNECT_TIME_LIMIT_MS = 30;
+        final int REGISTER_TIME_LIMIT_MS = 180;
+        assertTimeLimit("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> {
+            for (NetworkCallback cb : callbacks) {
+                mCm.registerNetworkCallback(request, cb);
+            }
+        });
+
+        final int CONNECT_TIME_LIMIT_MS = 40;
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         // Don't request that the network validate, because otherwise connect() will block until
         // the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired,
         // and we won't actually measure anything.
         mCellNetworkAgent.connect(false);
-        startTime = System.currentTimeMillis();
-        if (!availableLatch.await(CONNECT_TIME_LIMIT_MS, TimeUnit.MILLISECONDS)) {
-            fail(String.format("Only dispatched %d/%d onAvailable callbacks in %dms",
-                    NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
-                    CONNECT_TIME_LIMIT_MS));
-        }
-        timeTaken = System.currentTimeMillis() - startTime;
-        Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, CONNECT_TIME_LIMIT_MS));
 
-        final int SWITCH_TIME_LIMIT_MS = 30;
+        long onAvailableDispatchingDuration = durationOf(() -> {
+            if (!awaitLatch(availableLatch, CONNECT_TIME_LIMIT_MS)) {
+                fail(String.format("Only dispatched %d/%d onAvailable callbacks in %dms",
+                        NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
+                        CONNECT_TIME_LIMIT_MS));
+            }
+        });
+        Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
+                NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS));
+
+        final int SWITCH_TIME_LIMIT_MS = 40;
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         // Give wifi a high enough score that we'll linger cell when wifi comes up.
         mWiFiNetworkAgent.adjustScore(40);
         mWiFiNetworkAgent.connect(false);
-        startTime = System.currentTimeMillis();
-        if (!losingLatch.await(SWITCH_TIME_LIMIT_MS, TimeUnit.MILLISECONDS)) {
-            fail(String.format("Only dispatched %d/%d onLosing callbacks in %dms",
-                    NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, SWITCH_TIME_LIMIT_MS));
-        }
-        timeTaken = System.currentTimeMillis() - startTime;
+
+        long onLostDispatchingDuration = durationOf(() -> {
+            if (!awaitLatch(losingLatch, SWITCH_TIME_LIMIT_MS)) {
+                fail(String.format("Only dispatched %d/%d onLosing callbacks in %dms",
+                        NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, SWITCH_TIME_LIMIT_MS));
+            }
+        });
         Log.d(TAG, String.format("Linger, %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, SWITCH_TIME_LIMIT_MS));
+                NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS));
 
         final int UNREGISTER_TIME_LIMIT_MS = 10;
-        startTime = System.currentTimeMillis();
-        for (int i = 0; i < NUM_REQUESTS; i++) {
-            mCm.unregisterNetworkCallback(callbacks[i]);
-        }
-        timeTaken = System.currentTimeMillis() - startTime;
-        msg = String.format("Unregister %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, UNREGISTER_TIME_LIMIT_MS);
+        assertTimeLimit("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
+            for (NetworkCallback cb : callbacks) {
+                mCm.unregisterNetworkCallback(cb);
+            }
+        });
+    }
+
+    private long durationOf(Runnable fn) {
+        long startTime = SystemClock.elapsedRealtime();
+        fn.run();
+        return SystemClock.elapsedRealtime() - startTime;
+    }
+
+    private void assertTimeLimit(String descr, long timeLimit, Runnable fn) {
+        long timeTaken = durationOf(fn);
+        String msg = String.format("%s: took %dms, limit was %dms", descr, timeTaken, timeLimit);
         Log.d(TAG, msg);
-        assertTrue(msg, timeTaken < UNREGISTER_TIME_LIMIT_MS);
+        assertTrue(msg, timeTaken <= timeLimit);
+    }
+
+    private boolean awaitLatch(CountDownLatch l, long timeoutMs) {
+        try {
+            if (l.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+                return true;
+            }
+        } catch (InterruptedException e) {}
+        return false;
     }
 
     @SmallTest
@@ -2145,7 +2167,7 @@
             tracker.reevaluate();
             mService.waitForIdle();
             String msg = String.format("config=false, setting=%s", values[i]);
-            assertTrue(msg, mService.avoidBadWifi());
+            assertEventuallyTrue(() -> mService.avoidBadWifi(), 50);
             assertFalse(msg, tracker.shouldNotifyWifiUnvalidated());
         }
 
@@ -2154,19 +2176,19 @@
         Settings.Global.putInt(cr, settingName, 0);
         tracker.reevaluate();
         mService.waitForIdle();
-        assertFalse(mService.avoidBadWifi());
+        assertEventuallyTrue(() -> !mService.avoidBadWifi(), 50);
         assertFalse(tracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putInt(cr, settingName, 1);
         tracker.reevaluate();
         mService.waitForIdle();
-        assertTrue(mService.avoidBadWifi());
+        assertEventuallyTrue(() -> mService.avoidBadWifi(), 50);
         assertFalse(tracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putString(cr, settingName, null);
         tracker.reevaluate();
         mService.waitForIdle();
-        assertFalse(mService.avoidBadWifi());
+        assertEventuallyTrue(() -> !mService.avoidBadWifi(), 50);
         assertTrue(tracker.shouldNotifyWifiUnvalidated());
     }
 
@@ -2383,6 +2405,17 @@
         networkCallback.assertNoCallback();
     }
 
+    public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) throws Exception {
+        long start = SystemClock.elapsedRealtime();
+        while (SystemClock.elapsedRealtime() <= start + maxWaitingTimeMs) {
+            if (fn.getAsBoolean()) {
+                return;
+            }
+            Thread.sleep(10);
+        }
+        assertTrue(fn.getAsBoolean());
+    }
+
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
         public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
@@ -2472,6 +2505,7 @@
         return mWiFiNetworkAgent.getNetwork();
     }
 
+    @SmallTest
     public void testPacketKeepalives() throws Exception {
         InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
         InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index aed3635..84f0f90 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -71,7 +71,8 @@
                 "    transport_types: 3",
                 "  >",
                 "  time_ms: 1",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
@@ -93,7 +94,8 @@
                 "    state_transition: \"SomeState\"",
                 "  >",
                 "  time_ms: 1",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
@@ -114,7 +116,8 @@
                 "    state_transition: \"\"",
                 "  >",
                 "  time_ms: 1",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
@@ -160,7 +163,8 @@
                 "    return_codes: 178",
                 "  >",
                 "  time_ms: 1",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
@@ -181,7 +185,8 @@
                 "    latency_ms: 5678",
                 "  >",
                 "  time_ms: 1",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
@@ -200,7 +205,8 @@
                 "    if_name: \"wlan0\"",
                 "  >",
                 "  time_ms: 1",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
@@ -223,7 +229,8 @@
                 "    >",
                 "  >",
                 "  time_ms: 1",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
@@ -248,7 +255,8 @@
                 "    probe_result: 204",
                 "    probe_type: 1",
                 "  >",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
@@ -274,7 +282,8 @@
                 "    program_length: 2048",
                 "  >",
                 "  time_ms: 1",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
@@ -305,7 +314,8 @@
                 "    zero_lifetime_ras: 1",
                 "  >",
                 "  time_ms: 1",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
@@ -332,7 +342,8 @@
                 "    router_lifetime: 2000",
                 "  >",
                 "  time_ms: 1",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, ev);
     }
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 3fc89b9..aa491bb 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.net.ConnectivityMetricsEvent;
 import android.net.IIpConnectivityMetrics;
+import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
 import android.net.metrics.DefaultNetworkEvent;
 import android.net.metrics.DhcpClientEvent;
@@ -57,7 +58,7 @@
 
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mService = new IpConnectivityMetrics(mCtx);
+        mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
     }
 
     public void testLoggingEvents() throws Exception {
@@ -112,6 +113,27 @@
         assertEquals("", output3);
     }
 
+    public void testRateLimiting() {
+        final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
+        final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0);
+        final long fakeTimestamp = 1;
+
+        int attempt = 100; // More than burst quota, but less than buffer size.
+        for (int i = 0; i < attempt; i++) {
+            logger.log(ev);
+        }
+
+        String output1 = getdump("flush");
+        assertFalse("".equals(output1));
+
+        for (int i = 0; i < attempt; i++) {
+            assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
+        }
+
+        String output2 = getdump("flush");
+        assertEquals("", output2);
+    }
+
     public void testEndToEndLogging() {
         IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
 
@@ -204,7 +226,8 @@
                 "    router_lifetime: 2000",
                 "  >",
                 "  time_ms: 700",
-                ">");
+                ">",
+                "version: 2");
 
         verifySerialization(want, getdump("flush"));
     }
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index bce5787e..77956be 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -24,6 +24,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkMisc;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.text.format.DateUtils;
 import com.android.internal.R;
 import com.android.server.ConnectivityService;
@@ -70,6 +71,7 @@
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
     }
 
+    @SmallTest
     public void testTransitions() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         NetworkAgentInfo nai1 = wifiNai(100);
@@ -79,6 +81,7 @@
         assertFalse(mMonitor.isNotificationEnabled(nai2, nai1));
     }
 
+    @SmallTest
     public void testNotificationOnLinger() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -89,6 +92,7 @@
         verifyNotification(from, to);
     }
 
+    @SmallTest
     public void testToastOnLinger() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
@@ -99,6 +103,7 @@
         verifyToast(from, to);
     }
 
+    @SmallTest
     public void testNotificationClearedAfterDisconnect() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -112,6 +117,7 @@
         verify(mNotifier, times(1)).clearNotification(100);
     }
 
+    @SmallTest
     public void testNotificationClearedAfterSwitchingBack() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -125,6 +131,7 @@
         verify(mNotifier, times(1)).clearNotification(100);
     }
 
+    @SmallTest
     public void testUniqueToast() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
@@ -142,6 +149,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testMultipleNotifications() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -160,6 +168,7 @@
         verifyNotification(wifi2, cell);
     }
 
+    @SmallTest
     public void testRateLimiting() throws InterruptedException {
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, LOW_RATE_LIMIT);
 
@@ -185,6 +194,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testDailyLimiting() throws InterruptedException {
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, LOW_DAILY_LIMIT, HIGH_RATE_LIMIT);
 
@@ -211,6 +221,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testUniqueNotification() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -227,6 +238,7 @@
         verifyNotification(from, to);
     }
 
+    @SmallTest
     public void testIgnoreNeverValidatedNetworks() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch(transition(WIFI, CELLULAR));
@@ -238,6 +250,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testIgnoreCurrentlyValidatedNetworks() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch(transition(WIFI, CELLULAR));
@@ -249,6 +262,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testNoNotificationType() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch();
@@ -259,6 +273,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testNoTransitionToNotify() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NONE);
         setNotificationSwitch(transition(WIFI, CELLULAR));
@@ -269,6 +284,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testDifferentTransitionToNotify() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch(transition(CELLULAR, WIFI));
diff --git a/tests/net/java/com/android/server/connectivity/MetricsLoggerServiceTest.java b/tests/net/java/com/android/server/connectivity/MetricsLoggerServiceTest.java
index 5f84ea1..5981f48 100644
--- a/tests/net/java/com/android/server/connectivity/MetricsLoggerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/MetricsLoggerServiceTest.java
@@ -20,6 +20,7 @@
 import android.net.ConnectivityMetricsEvent;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.test.suitebuilder.annotation.SmallTest;
 import static android.net.ConnectivityMetricsEvent.Reference;
 
 import junit.framework.TestCase;
@@ -67,12 +68,14 @@
         mService.onStart();
     }
 
+    @SmallTest
     public void testGetNoEvents() throws Exception {
         Reference r = new Reference(0);
         assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
         assertEquals(0, r.getValue());
     }
 
+    @SmallTest
     public void testLogAndGetEvents() throws Exception {
         mService.mBinder.logEvents(EVENTS);
 
@@ -85,6 +88,7 @@
         assertEquals(N_EVENTS, r.getValue());
     }
 
+    @SmallTest
     public void testLogOneByOne() throws Exception {
         for (ConnectivityMetricsEvent ev : EVENTS) {
             mService.mBinder.logEvent(ev);
@@ -99,6 +103,7 @@
         assertEquals(N_EVENTS, r.getValue());
     }
 
+    @SmallTest
     public void testInterleavedLogAndGet() throws Exception {
         mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 0, 3));
 
@@ -117,6 +122,7 @@
         assertEquals(N_EVENTS, r.getValue());
     }
 
+    @SmallTest
     public void testMultipleGetAll() throws Exception {
         mService.mBinder.logEvents(Arrays.copyOf(EVENTS, 3));
 
@@ -131,6 +137,7 @@
         assertEquals(N_EVENTS, r2.getValue());
     }
 
+    @SmallTest
     public void testLogAndDumpConcurrently() throws Exception {
         for (int i = 0; i < 50; i++) {
             mContext = null;
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 9e2fd62..2bb62bb 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -22,6 +22,8 @@
 import android.net.metrics.DnsEvent;
 import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import junit.framework.TestCase;
 import org.junit.Before;
@@ -82,6 +84,7 @@
         verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture());
     }
 
+    @SmallTest
     public void testOneBatch() throws Exception {
         log(105, LATENCIES);
         log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event
@@ -96,6 +99,7 @@
             new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES));
     }
 
+    @SmallTest
     public void testSeveralBatches() throws Exception {
         log(105, LATENCIES);
         log(106, LATENCIES);
@@ -109,6 +113,7 @@
             new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
     }
 
+    @SmallTest
     public void testBatchAndNetworkLost() throws Exception {
         byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20);
         byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20);
@@ -125,6 +130,7 @@
             new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
     }
 
+    @SmallTest
     public void testConcurrentBatchesAndDumps() throws Exception {
         final long stop = System.currentTimeMillis() + 100;
         final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
@@ -146,6 +152,7 @@
             new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
     }
 
+    @SmallTest
     public void testConcurrentBatchesAndNetworkLoss() throws Exception {
         logAsync(105, LATENCIES);
         Thread.sleep(10L);
@@ -157,9 +164,13 @@
     }
 
     void log(int netId, int[] latencies) {
-        for (int l : latencies) {
-            mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l, null, null, 0,
-                    0);
+        try {
+            for (int l : latencies) {
+                mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l, null, null,
+                        0, 0);
+            }
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
         }
     }
 
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 5d8b843..b51b277 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -25,9 +25,11 @@
 
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
+import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.net.NetworkInfo.DetailedState;
 import android.net.UidRange;
 import android.os.INetworkManagementService;
 import android.os.Looper;
@@ -43,6 +45,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -88,14 +92,18 @@
     @Mock private PackageManager mPackageManager;
     @Mock private INetworkManagementService mNetService;
     @Mock private AppOpsManager mAppOps;
+    @Mock private NotificationManager mNotificationManager;
 
     @Override
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         setMockedPackages(mPackages);
+        when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
         when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
+        when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
+                .thenReturn(mNotificationManager);
         doNothing().when(mNetService).registerObserver(any());
     }
 
@@ -103,7 +111,7 @@
     public void testRestrictedProfilesAreAddedToVpn() {
         setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
 
-        final Vpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
                 null, null);
 
@@ -117,7 +125,7 @@
     public void testManagedProfilesAreNotAddedToVpn() {
         setMockedUsers(primaryUser, managedProfileA);
 
-        final Vpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
                 null, null);
 
@@ -130,7 +138,7 @@
     public void testAddUserToVpnOnlyAddsOneUser() {
         setMockedUsers(primaryUser, restrictedProfileA, managedProfileA);
 
-        final Vpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         final Set<UidRange> ranges = new ArraySet<>();
         vpn.addUserToRanges(ranges, primaryUser.id, null, null);
 
@@ -141,7 +149,7 @@
 
     @SmallTest
     public void testUidWhiteAndBlacklist() throws Exception {
-        final Vpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
         final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
 
@@ -166,15 +174,15 @@
 
     @SmallTest
     public void testLockdownChangingPackage() throws Exception {
-        final MockVpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
 
         // Default state.
-        vpn.assertUnblocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on without lockdown.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
-        vpn.assertUnblocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on with lockdown.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
@@ -182,8 +190,8 @@
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
         }));
-        vpn.assertBlocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
-        vpn.assertUnblocked(user.start + PKG_UIDS[1]);
+        assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[1]);
 
         // Switch to another app.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
@@ -195,13 +203,13 @@
             new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
             new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
         }));
-        vpn.assertBlocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
-        vpn.assertUnblocked(user.start + PKG_UIDS[3]);
+        assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[3]);
     }
 
     @SmallTest
     public void testLockdownAddingAProfile() throws Exception {
-        final MockVpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         setMockedUsers(primaryUser);
 
         // Make a copy of the restricted profile, as we're going to mark it deleted halfway through.
@@ -220,7 +228,7 @@
         }));
 
         // Verify restricted user isn't affected at first.
-        vpn.assertUnblocked(profile.start + PKG_UIDS[0]);
+        assertUnblocked(vpn, profile.start + PKG_UIDS[0]);
 
         // Add the restricted user.
         setMockedUsers(primaryUser, tempProfile);
@@ -239,24 +247,53 @@
         }));
     }
 
+    @SmallTest
+    public void testNotificationShownForAlwaysOnApp() {
+        final Vpn vpn = spyVpn(primaryUser.id);
+        final InOrder order = inOrder(vpn);
+        setMockedUsers(primaryUser);
+
+        // Don't show a notification for regular disconnected states.
+        vpn.updateState(DetailedState.DISCONNECTED, TAG);
+        order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+
+        // Start showing a notification for disconnected once always-on.
+        vpn.setAlwaysOnPackage(PKGS[0], false);
+        order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+
+        // Stop showing the notification once connected.
+        vpn.updateState(DetailedState.CONNECTED, TAG);
+        order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+
+        // Show the notification if we disconnect again.
+        vpn.updateState(DetailedState.DISCONNECTED, TAG);
+        order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+
+        // Notification should be cleared after unsetting always-on package.
+        vpn.setAlwaysOnPackage(null, false);
+        order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+    }
+
     /**
-     * A subclass of {@link Vpn} with some of the fields pre-mocked.
+     * Mock some methods of vpn object.
      */
-    private class MockVpn extends Vpn {
-        public MockVpn(@UserIdInt int userId) {
-            super(Looper.myLooper(), mContext, mNetService, userId);
-        }
+    private Vpn spyVpn(@UserIdInt int userId) {
+        final Vpn vpn = spy(new Vpn(Looper.myLooper(), mContext, mNetService, userId));
 
-        public void assertBlocked(int... uids) {
-            for (int uid : uids) {
-                assertTrue("Uid " + uid + " should be blocked", isBlockingUid(uid));
-            }
-        }
+        // Block calls to the NotificationManager or PendingIntent#getActivity.
+        doNothing().when(vpn).updateAlwaysOnNotificationInternal(anyBoolean());
+        return vpn;
+    }
 
-        public void assertUnblocked(int... uids) {
-            for (int uid : uids) {
-                assertFalse("Uid " + uid + " should not be blocked", isBlockingUid(uid));
-            }
+    private static void assertBlocked(Vpn vpn, int... uids) {
+        for (int uid : uids) {
+            assertTrue("Uid " + uid + " should be blocked", vpn.isBlockingUid(uid));
+        }
+    }
+
+    private static void assertUnblocked(Vpn vpn, int... uids) {
+        for (int uid : uids) {
+            assertFalse("Uid " + uid + " should not be blocked", vpn.isBlockingUid(uid));
         }
     }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java
index 7e361a1..96cba78 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java
@@ -40,6 +40,11 @@
     }
 
     @Override
+    public int pos() {
+        return mByteBuffer.position();
+    }
+
+    @Override
     public void skip(int byteCount) {
         int newPosition = mByteBuffer.position() + byteCount;
         assert newPosition <= mSize;
diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk
index 769db6b..d3ee1d3 100644
--- a/tools/preload2/Android.mk
+++ b/tools/preload2/Android.mk
@@ -17,6 +17,8 @@
 LOCAL_MODULE:= preload2
 
 include $(BUILD_HOST_JAVA_LIBRARY)
+# Copy to build artifacts
+$(call dist-for-goals,dist_files,$(LOCAL_BUILT_MODULE):$(LOCAL_MODULE).jar)
 
 # Copy the preload-tool shell script to the host's bin directory.
 include $(CLEAR_VARS)
diff --git a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
new file mode 100755
index 0000000..c328748
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.Manifest.permission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.net.INetworkScoreCache;
+import android.net.NetworkKey;
+import android.net.ScoredNetwork;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@link INetworkScoreCache} implementation for Wifi Networks.
+ *
+ * @hide
+ */
+public class WifiNetworkScoreCache extends INetworkScoreCache.Stub {
+    private static final String TAG = "WifiNetworkScoreCache";
+    private static final boolean DBG = false;
+
+    // A Network scorer returns a score in the range [-128, +127]
+    // We treat the lowest possible score as though there were no score, effectively allowing the
+    // scorer to provide an RSSI threshold below which a network should not be used.
+    public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE;
+    private final Context mContext;
+
+    // The key is of the form "<ssid>"<bssid>
+    // TODO: What about SSIDs that can't be encoded as UTF-8?
+    private final Map<String, ScoredNetwork> mNetworkCache;
+
+    public WifiNetworkScoreCache(Context context) {
+        mContext = context;
+        mNetworkCache = new HashMap<String, ScoredNetwork>();
+    }
+
+    @Override public final void updateScores(List<ScoredNetwork> networks) {
+      if (networks == null) {
+           return;
+       }
+       Log.e(TAG, "updateScores list size=" + networks.size());
+
+       synchronized(mNetworkCache) {
+           for (ScoredNetwork network : networks) {
+               String networkKey = buildNetworkKey(network);
+               if (networkKey == null) continue;
+               mNetworkCache.put(networkKey, network);
+           }
+       }
+    }
+
+    @Override public final void clearScores() {
+        synchronized (mNetworkCache) {
+            mNetworkCache.clear();
+        }
+    }
+
+    /**
+     * Returns whether there is any score info for the given ScanResult.
+     *
+     * This includes null-score info, so it should only be used when determining whether to request
+     * scores from the network scorer.
+     */
+    public boolean isScoredNetwork(ScanResult result) {
+        return getScoredNetwork(result) != null;
+    }
+
+    /**
+     * Returns whether there is a non-null score curve for the given ScanResult.
+     *
+     * A null score curve has special meaning - we should never connect to an ephemeral network if
+     * the score curve is null.
+     */
+    public boolean hasScoreCurve(ScanResult result) {
+        ScoredNetwork network = getScoredNetwork(result);
+        return network != null && network.rssiCurve != null;
+    }
+
+    public int getNetworkScore(ScanResult result) {
+
+        int score = INVALID_NETWORK_SCORE;
+
+        ScoredNetwork network = getScoredNetwork(result);
+        if (network != null && network.rssiCurve != null) {
+            score = network.rssiCurve.lookupScore(result.level);
+            if (DBG) {
+                Log.e(TAG, "getNetworkScore found scored network " + network.networkKey
+                        + " score " + Integer.toString(score)
+                        + " RSSI " + result.level);
+            }
+        }
+        return score;
+    }
+
+    /**
+     * Returns the ScoredNetwork metered hint for a given ScanResult.
+     *
+     * If there is no ScoredNetwork associated with the ScanResult then false will be returned.
+     */
+    public boolean getMeteredHint(ScanResult result) {
+        ScoredNetwork network = getScoredNetwork(result);
+        return network != null && network.meteredHint;
+    }
+
+    public int getNetworkScore(ScanResult result, boolean isActiveNetwork) {
+
+        int score = INVALID_NETWORK_SCORE;
+
+        ScoredNetwork network = getScoredNetwork(result);
+        if (network != null && network.rssiCurve != null) {
+            score = network.rssiCurve.lookupScore(result.level, isActiveNetwork);
+            if (DBG) {
+                Log.e(TAG, "getNetworkScore found scored network " + network.networkKey
+                        + " score " + Integer.toString(score)
+                        + " RSSI " + result.level
+                        + " isActiveNetwork " + isActiveNetwork);
+            }
+        }
+        return score;
+    }
+
+    private ScoredNetwork getScoredNetwork(ScanResult result) {
+        String key = buildNetworkKey(result);
+        if (key == null) return null;
+
+        //find it
+        synchronized(mNetworkCache) {
+            ScoredNetwork network = mNetworkCache.get(key);
+            return network;
+        }
+    }
+
+     private String buildNetworkKey(ScoredNetwork network) {
+        if (network == null || network.networkKey == null) return null;
+        if (network.networkKey.wifiKey == null) return null;
+        if (network.networkKey.type == NetworkKey.TYPE_WIFI) {
+            String key = network.networkKey.wifiKey.ssid;
+            if (key == null) return null;
+            if (network.networkKey.wifiKey.bssid != null) {
+                key = key + network.networkKey.wifiKey.bssid;
+            }
+            return key;
+        }
+        return null;
+    }
+
+    private String buildNetworkKey(ScanResult result) {
+        if (result == null || result.SSID == null) {
+            return null;
+        }
+        StringBuilder key = new StringBuilder("\"");
+        key.append(result.SSID);
+        key.append("\"");
+        if (result.BSSID != null) {
+            key.append(result.BSSID);
+        }
+        return key.toString();
+    }
+
+    @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
+        writer.println("WifiNetworkScoreCache");
+        writer.println("  All score curves:");
+        for (Map.Entry<String, ScoredNetwork> entry : mNetworkCache.entrySet()) {
+            ScoredNetwork scoredNetwork = entry.getValue();
+            writer.println("    " + entry.getKey() + ": " + scoredNetwork.rssiCurve
+                    + ", meteredHint=" + scoredNetwork.meteredHint);
+        }
+        writer.println("  Current network scores:");
+        WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        for (ScanResult scanResult : wifiManager.getScanResults()) {
+            writer.println("    " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult));
+        }
+    }
+
+}
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
index 5850fee..eac49d2 100644
--- a/wifi/tests/Android.mk
+++ b/wifi/tests/Android.mk
@@ -50,6 +50,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	android-support-test \
+	guava \
 	mockito-target-minus-junit4 \
 	frameworks-base-testutils \
 
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
new file mode 100644
index 0000000..f8549b9
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.NetworkKey;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.net.WifiKey;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Rule;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link WifiNetworkScoreCache}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WifiNetworkScoreCacheTest {
+
+  @Mock public Context mockContext; // isn't used, can be null
+  @Mock private RssiCurve mockRssiCurve;
+
+  public static final String SSID = "ssid";
+  public static final String FORMATTED_SSID = "\"" + SSID + "\"";
+  public static final String BSSID = "AA:AA:AA:AA:AA:AA";
+
+  public static final WifiKey VALID_KEY = new WifiKey(FORMATTED_SSID, BSSID);
+
+  public static final ScanResult VALID_SCAN_RESULT = buildScanResult(SSID, BSSID);
+
+  private ScoredNetwork mValidScoredNetwork;
+  private WifiNetworkScoreCache mScoreCache =
+      new WifiNetworkScoreCache(mockContext);
+
+  private static ScanResult buildScanResult(String ssid, String bssid) {
+    return new ScanResult(
+         WifiSsid.createFromAsciiEncoded(ssid),
+         bssid,
+         "" /* caps */,
+         0 /* level */,
+         0 /* frequency */,
+         0 /* tsf */,
+         0 /* distCm */,
+         0 /* distSdCm*/);
+  }
+
+  private static ScoredNetwork buildScoredNetwork(WifiKey key, RssiCurve curve) {
+    return new ScoredNetwork(new NetworkKey(key), curve);
+  }
+
+  // Called from setup
+  private void initializeCacheWithValidScoredNetwork() {
+    mScoreCache.updateScores(ImmutableList.of(mValidScoredNetwork));
+  }
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    mValidScoredNetwork = buildScoredNetwork(VALID_KEY, mockRssiCurve);
+    mScoreCache = new WifiNetworkScoreCache(mockContext);
+    initializeCacheWithValidScoredNetwork();
+  }
+
+
+  @Test
+  public void isScoredNetworkShouldReturnTrueAfterUpdateScoresIsCalled() {
+    assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+  }
+
+  @Test
+  public void isScoredNetworkShouldReturnFalseAfterClearScoresIsCalled() {
+    mScoreCache.clearScores();
+    assertFalse(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+  }
+
+  @Test
+  public void updateScoresShouldAddNewNetwork() {
+    WifiKey key2 = new WifiKey("\"ssid2\"", BSSID);
+    ScoredNetwork network2 = buildScoredNetwork(key2, mockRssiCurve);
+    ScanResult result2 = buildScanResult("ssid2", BSSID);
+
+    mScoreCache.updateScores(ImmutableList.of(network2));
+
+    assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+    assertTrue(mScoreCache.isScoredNetwork(result2));
+  }
+
+  @Test
+  public void hasScoreCurveShouldReturnTrue() {
+    assertTrue(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
+  }
+
+  @Test
+  public void hasScoreCurveShouldReturnFalseWhenNoCachedNetwork() {
+    ScanResult unscored = buildScanResult("fake", BSSID);
+    assertFalse(mScoreCache.hasScoreCurve(unscored));
+  }
+
+  @Test
+  public void hasScoreCurveShouldReturnFalseWhenScoredNetworkHasNoCurve() {
+    ScoredNetwork noCurve = buildScoredNetwork(VALID_KEY, null /* rssiCurve */);
+    mScoreCache.updateScores(ImmutableList.of(noCurve));
+
+    assertFalse(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
+  }
+
+  @Test
+  public void getNetworkScoreShouldReturnScore() {
+    final byte score = 50;
+    final int rssi = -70;
+    ScanResult result = new ScanResult(VALID_SCAN_RESULT);
+    result.level = rssi;
+
+    when(mockRssiCurve.lookupScore(rssi)).thenReturn(score);
+
+    assertEquals(score, mScoreCache.getNetworkScore(result));
+  }
+
+  @Test
+  public void getMeteredHintShouldReturnFalse() {
+    assertFalse(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
+  }
+
+  @Test
+  public void getMeteredHintShouldReturnTrue() {
+    ScoredNetwork network =
+        new ScoredNetwork(new NetworkKey(VALID_KEY), mockRssiCurve, true /* metered Hint */);
+    mScoreCache.updateScores(ImmutableList.of(network));
+
+    assertTrue(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
+  }
+}