Merge "Fix security problem on PermissionMonitor#hasPermission" into oc-dev am: 5e0b069876

Change-Id: I4c255820617a86afdfa239febdd46fff7d082381
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index e64cbcd..f478071 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -15,8 +15,6 @@
  */
 package android.net;
 
-import static com.android.internal.util.Preconditions.checkNotNull;
-
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -43,25 +41,27 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.Protocol;
-import com.android.internal.util.MessageUtils;
 
 import libcore.net.event.NetworkEventDispatcher;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
+import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Class that answers queries about the state of network connectivity. It also
@@ -602,6 +602,15 @@
     public final static int REQUEST_ID_UNSET = 0;
 
     /**
+     * Static unique request used as a tombstone for NetworkCallbacks that have been unregistered.
+     * This allows to distinguish when unregistering NetworkCallbacks those that were never
+     * registered and those that were already unregistered.
+     * @hide
+     */
+    private final static NetworkRequest ALREADY_UNREGISTERED =
+            new NetworkRequest.Builder().clearCapabilities().build();
+
+    /**
      * A NetID indicating no Network is selected.
      * Keep in sync with bionic/libc/dns/include/resolv_netid.h
      * @hide
@@ -1465,9 +1474,7 @@
         // Map from type to transports.
         final int NOT_FOUND = -1;
         final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND);
-        if (transport == NOT_FOUND) {
-            throw new IllegalArgumentException("unknown legacy type: " + type);
-        }
+        Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type);
         nc.addTransportType(transport);
 
         // Map from type to capabilities.
@@ -1555,8 +1562,8 @@
         }
 
         private PacketKeepalive(Network network, PacketKeepaliveCallback callback) {
-            checkNotNull(network, "network cannot be null");
-            checkNotNull(callback, "callback cannot be null");
+            Preconditions.checkNotNull(network, "network cannot be null");
+            Preconditions.checkNotNull(callback, "callback cannot be null");
             mNetwork = network;
             mCallback = callback;
             HandlerThread thread = new HandlerThread(TAG);
@@ -1815,9 +1822,7 @@
      */
     public void removeDefaultNetworkActiveListener(OnNetworkActiveListener l) {
         INetworkActivityListener rl = mNetworkActivityListeners.get(l);
-        if (rl == null) {
-            throw new IllegalArgumentException("Listener not registered: " + l);
-        }
+        Preconditions.checkArgument(rl != null, "Listener was not registered.");
         try {
             getNetworkManagementService().unregisterNetworkActivityListener(rl);
         } catch (RemoteException e) {
@@ -1845,8 +1850,8 @@
      * {@hide}
      */
     public ConnectivityManager(Context context, IConnectivityManager service) {
-        mContext = checkNotNull(context, "missing context");
-        mService = checkNotNull(service, "missing IConnectivityManager");
+        mContext = Preconditions.checkNotNull(context, "missing context");
+        mService = Preconditions.checkNotNull(service, "missing IConnectivityManager");
         sInstance = this;
     }
 
@@ -1872,8 +1877,11 @@
                 .getPackageNameForUid(context, uid), true /* throwException */);
     }
 
-    /** {@hide */
-    public static final void enforceTetherChangePermission(Context context) {
+    /** {@hide} */
+    public static final void enforceTetherChangePermission(Context context, String callingPkg) {
+        Preconditions.checkNotNull(context, "Context cannot be null");
+        Preconditions.checkNotNull(callingPkg, "callingPkg cannot be null");
+
         if (context.getResources().getStringArray(
                 com.android.internal.R.array.config_mobile_hotspot_provision_app).length == 2) {
             // Have a provisioning app - must only let system apps (which check this app)
@@ -1882,8 +1890,10 @@
                     android.Manifest.permission.TETHER_PRIVILEGED, "ConnectivityService");
         } else {
             int uid = Binder.getCallingUid();
-            Settings.checkAndNoteWriteSettingsOperation(context, uid, Settings
-                    .getPackageNameForUid(context, uid), true /* throwException */);
+            // If callingPkg's uid is not same as Binder.getCallingUid(),
+            // AppOpsService throws SecurityException.
+            Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPkg,
+                    true /* throwException */);
         }
     }
 
@@ -2005,7 +2015,9 @@
      */
     public int tether(String iface) {
         try {
-            return mService.tether(iface);
+            String pkgName = mContext.getOpPackageName();
+            Log.i(TAG, "tether caller:" + pkgName);
+            return mService.tether(iface, pkgName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2031,7 +2043,9 @@
      */
     public int untether(String iface) {
         try {
-            return mService.untether(iface);
+            String pkgName = mContext.getOpPackageName();
+            Log.i(TAG, "untether caller:" + pkgName);
+            return mService.untether(iface, pkgName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2108,7 +2122,7 @@
     @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
     public void startTethering(int type, boolean showProvisioningUi,
             final OnStartTetheringCallback callback, Handler handler) {
-        checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
+        Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
 
         ResultReceiver wrappedCallback = new ResultReceiver(handler) {
             @Override
@@ -2122,7 +2136,9 @@
         };
 
         try {
-            mService.startTethering(type, wrappedCallback, showProvisioningUi);
+            String pkgName = mContext.getOpPackageName();
+            Log.i(TAG, "startTethering caller:" + pkgName);
+            mService.startTethering(type, wrappedCallback, showProvisioningUi, pkgName);
         } catch (RemoteException e) {
             Log.e(TAG, "Exception trying to start tethering.", e);
             wrappedCallback.send(TETHER_ERROR_SERVICE_UNAVAIL, null);
@@ -2143,7 +2159,9 @@
     @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
     public void stopTethering(int type) {
         try {
-            mService.stopTethering(type);
+            String pkgName = mContext.getOpPackageName();
+            Log.i(TAG, "stopTethering caller:" + pkgName);
+            mService.stopTethering(type, pkgName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2225,7 +2243,9 @@
      */
     public int setUsbTethering(boolean enable) {
         try {
-            return mService.setUsbTethering(enable);
+            String pkgName = mContext.getOpPackageName();
+            Log.i(TAG, "setUsbTethering caller:" + pkgName);
+            return mService.setUsbTethering(enable, pkgName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2563,8 +2583,16 @@
     }
 
     /**
-     * Base class for NetworkRequest callbacks.  Used for notifications about network
-     * changes.  Should be extended by applications wanting notifications.
+     * Base class for {@code NetworkRequest} callbacks. Used for notifications about network
+     * changes. Should be extended by applications wanting notifications.
+     *
+     * A {@code NetworkCallback} is registered by calling
+     * {@link #requestNetwork(NetworkRequest, NetworkCallback)},
+     * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)},
+     * or {@link #registerDefaultNetworkCallback(NetworkCallback). A {@code NetworkCallback} is
+     * unregistered by calling {@link #unregisterNetworkCallback(NetworkCallback)}.
+     * A {@code NetworkCallback} should be registered at most once at any time.
+     * A {@code NetworkCallback} that has been unregistered can be registered again.
      */
     public static class NetworkCallback {
         /**
@@ -2619,7 +2647,7 @@
 
         /**
          * Called if no network is found in the timeout time specified in
-         * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)} call. This callback is not
+         * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call. This callback is not
          * called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)}
          * without timeout. When this callback is invoked the associated
          * {@link NetworkRequest} will have already been removed and released, as if
@@ -2669,6 +2697,28 @@
         private NetworkRequest networkRequest;
     }
 
+    /**
+     * Constant error codes used by ConnectivityService to communicate about failures and errors
+     * across a Binder boundary.
+     * @hide
+     */
+    public interface Errors {
+        static int TOO_MANY_REQUESTS = 1;
+    }
+
+    /** @hide */
+    public static class TooManyRequestsException extends RuntimeException {}
+
+    private static RuntimeException convertServiceException(ServiceSpecificException e) {
+        switch (e.errorCode) {
+            case Errors.TOO_MANY_REQUESTS:
+                return new TooManyRequestsException();
+            default:
+                Log.w(TAG, "Unknown service error code " + e.errorCode);
+                return new RuntimeException(e);
+        }
+    }
+
     private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
     /** @hide */
     public static final int CALLBACK_PRECHECK            = BASE + 1;
@@ -2684,17 +2734,12 @@
     public static final int CALLBACK_CAP_CHANGED         = BASE + 6;
     /** @hide */
     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 */
-    private static final int EXPIRE_LEGACY_REQUEST       = BASE + 10;
+    private static final int EXPIRE_LEGACY_REQUEST       = BASE + 8;
     /** @hide */
-    public static final int CALLBACK_SUSPENDED           = BASE + 11;
+    public static final int CALLBACK_SUSPENDED           = BASE + 9;
     /** @hide */
-    public static final int CALLBACK_RESUMED             = BASE + 12;
+    public static final int CALLBACK_RESUMED             = BASE + 10;
 
     /** @hide */
     public static String getCallbackName(int whichCallback) {
@@ -2706,8 +2751,6 @@
             case CALLBACK_UNAVAIL:      return "CALLBACK_UNAVAIL";
             case CALLBACK_CAP_CHANGED:  return "CALLBACK_CAP_CHANGED";
             case CALLBACK_IP_CHANGED:   return "CALLBACK_IP_CHANGED";
-            case CALLBACK_RELEASED:     return "CALLBACK_RELEASED";
-            case CALLBACK_EXIT:         return "CALLBACK_EXIT";
             case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST";
             case CALLBACK_SUSPENDED:    return "CALLBACK_SUSPENDED";
             case CALLBACK_RESUMED:      return "CALLBACK_RESUMED";
@@ -2725,97 +2768,67 @@
         }
 
         CallbackHandler(Handler handler) {
-            this(handler.getLooper());
+            this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper());
         }
 
         @Override
         public void handleMessage(Message message) {
-            NetworkRequest request = getObject(message, NetworkRequest.class);
-            Network network = getObject(message, Network.class);
-            if (DBG) {
-                Log.d(TAG, whatToString(message.what) + " for network " + network);
+            if (message.what == EXPIRE_LEGACY_REQUEST) {
+                expireRequest((NetworkCapabilities) message.obj, message.arg1);
+                return;
             }
+
+            final NetworkRequest request = getObject(message, NetworkRequest.class);
+            final Network network = getObject(message, Network.class);
+            final NetworkCallback callback;
+            synchronized (sCallbacks) {
+                callback = sCallbacks.get(request);
+            }
+            if (DBG) {
+                Log.d(TAG, getCallbackName(message.what) + " for network " + network);
+            }
+            if (callback == null) {
+                Log.w(TAG, "callback not found for " + getCallbackName(message.what) + " message");
+                return;
+            }
+
             switch (message.what) {
                 case CALLBACK_PRECHECK: {
-                    NetworkCallback callback = getCallback(request, "PRECHECK");
-                    if (callback != null) {
-                        callback.onPreCheck(network);
-                    }
+                    callback.onPreCheck(network);
                     break;
                 }
                 case CALLBACK_AVAILABLE: {
-                    NetworkCallback callback = getCallback(request, "AVAILABLE");
-                    if (callback != null) {
-                        callback.onAvailable(network);
-                    }
+                    callback.onAvailable(network);
                     break;
                 }
                 case CALLBACK_LOSING: {
-                    NetworkCallback callback = getCallback(request, "LOSING");
-                    if (callback != null) {
-                        callback.onLosing(network, message.arg1);
-                    }
+                    callback.onLosing(network, message.arg1);
                     break;
                 }
                 case CALLBACK_LOST: {
-                    NetworkCallback callback = getCallback(request, "LOST");
-                    if (callback != null) {
-                        callback.onLost(network);
-                    }
+                    callback.onLost(network);
                     break;
                 }
                 case CALLBACK_UNAVAIL: {
-                    NetworkCallback callback = getCallback(request, "UNAVAIL");
-                    if (callback != null) {
-                        callback.onUnavailable();
-                    }
+                    callback.onUnavailable();
                     break;
                 }
                 case CALLBACK_CAP_CHANGED: {
-                    NetworkCallback callback = getCallback(request, "CAP_CHANGED");
-                    if (callback != null) {
-                        NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
-                        callback.onCapabilitiesChanged(network, cap);
-                    }
+                    NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
+                    callback.onCapabilitiesChanged(network, cap);
                     break;
                 }
                 case CALLBACK_IP_CHANGED: {
-                    NetworkCallback callback = getCallback(request, "IP_CHANGED");
-                    if (callback != null) {
-                        LinkProperties lp = getObject(message, LinkProperties.class);
-                        callback.onLinkPropertiesChanged(network, lp);
-                    }
+                    LinkProperties lp = getObject(message, LinkProperties.class);
+                    callback.onLinkPropertiesChanged(network, lp);
                     break;
                 }
                 case CALLBACK_SUSPENDED: {
-                    NetworkCallback callback = getCallback(request, "SUSPENDED");
-                    if (callback != null) {
-                        callback.onNetworkSuspended(network);
-                    }
+                    callback.onNetworkSuspended(network);
                     break;
                 }
                 case CALLBACK_RESUMED: {
-                    NetworkCallback callback = getCallback(request, "RESUMED");
-                    if (callback != null) {
-                        callback.onNetworkResumed(network);
-                    }
-                    break;
-                }
-                case CALLBACK_RELEASED: {
-                    final NetworkCallback callback;
-                    synchronized(sCallbacks) {
-                        callback = sCallbacks.remove(request);
-                    }
-                    if (callback == null) {
-                        Log.e(TAG, "callback not found for RELEASED message");
-                    }
-                    break;
-                }
-                case CALLBACK_EXIT: {
-                    break;
-                }
-                case EXPIRE_LEGACY_REQUEST: {
-                    expireRequest((NetworkCapabilities)message.obj, message.arg1);
+                    callback.onNetworkResumed(network);
                     break;
                 }
             }
@@ -2824,17 +2837,6 @@
         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(sCallbacks) {
-                callback = sCallbacks.get(req);
-            }
-            if (callback == null) {
-                Log.e(TAG, "callback not found for " + name + " message");
-            }
-            return callback;
-        }
     }
 
     private CallbackHandler getDefaultHandler() {
@@ -2854,17 +2856,17 @@
 
     private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
             int timeoutMs, int action, int legacyType, CallbackHandler handler) {
-        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
+        checkCallbackNotNull(callback);
+        Preconditions.checkArgument(action == REQUEST || need != null, "null NetworkCapabilities");
         final NetworkRequest request;
         try {
             synchronized(sCallbacks) {
+                if (callback.networkRequest != null
+                        && callback.networkRequest != ALREADY_UNREGISTERED) {
+                    // TODO: throw exception instead and enforce 1:1 mapping of callbacks
+                    // and requests (http://b/20701525).
+                    Log.e(TAG, "NetworkCallback was already registered");
+                }
                 Messenger messenger = new Messenger(handler);
                 Binder binder = new Binder();
                 if (action == LISTEN) {
@@ -2880,6 +2882,8 @@
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw convertServiceException(e);
         }
         return request;
     }
@@ -2908,7 +2912,7 @@
      * This {@link NetworkRequest} will live until released via
      * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
      * version of the method which takes a timeout is
-     * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
+     * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
      * Status of the request can be followed by listening to the various
      * callbacks described in {@link NetworkCallback}.  The {@link Network}
      * can be used to direct traffic to the network.
@@ -2943,7 +2947,7 @@
      * This {@link NetworkRequest} will live until released via
      * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
      * version of the method which takes a timeout is
-     * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
+     * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
      * Status of the request can be followed by listening to the various
      * callbacks described in {@link NetworkCallback}.  The {@link Network}
      * can be used to direct traffic to the network.
@@ -3005,14 +3009,11 @@
      */
     public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
             int timeoutMs) {
-        if (timeoutMs <= 0) {
-            throw new IllegalArgumentException("Non-positive timeoutMs: " + timeoutMs);
-        }
+        checkTimeout(timeoutMs);
         int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
         requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
     }
 
-
     /**
      * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
      * by a timeout.
@@ -3042,9 +3043,7 @@
      */
     public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
             Handler handler, int timeoutMs) {
-        if (timeoutMs <= 0) {
-            throw new IllegalArgumentException("Non-positive timeoutMs");
-        }
+        checkTimeout(timeoutMs);
         int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
         CallbackHandler cbHandler = new CallbackHandler(handler);
         requestNetwork(request, networkCallback, timeoutMs, legacyType, cbHandler);
@@ -3116,11 +3115,13 @@
      *         {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}.
      */
     public void requestNetwork(NetworkRequest request, PendingIntent operation) {
-        checkPendingIntent(operation);
+        checkPendingIntentNotNull(operation);
         try {
             mService.pendingRequestForNetwork(request.networkCapabilities, operation);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw convertServiceException(e);
         }
     }
 
@@ -3137,7 +3138,7 @@
      *                  corresponding NetworkRequest you'd like to remove. Cannot be null.
      */
     public void releaseNetworkRequest(PendingIntent operation) {
-        checkPendingIntent(operation);
+        checkPendingIntentNotNull(operation);
         try {
             mService.releasePendingNetworkRequest(operation);
         } catch (RemoteException e) {
@@ -3145,10 +3146,16 @@
         }
     }
 
-    private void checkPendingIntent(PendingIntent intent) {
-        if (intent == null) {
-            throw new IllegalArgumentException("PendingIntent cannot be null.");
-        }
+    private static void checkPendingIntentNotNull(PendingIntent intent) {
+        Preconditions.checkNotNull(intent, "PendingIntent cannot be null.");
+    }
+
+    private static void checkCallbackNotNull(NetworkCallback callback) {
+        Preconditions.checkNotNull(callback, "null NetworkCallback");
+    }
+
+    private static void checkTimeout(int timeoutMs) {
+        Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive.");
     }
 
     /**
@@ -3215,11 +3222,13 @@
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public void registerNetworkCallback(NetworkRequest request, PendingIntent operation) {
-        checkPendingIntent(operation);
+        checkPendingIntentNotNull(operation);
         try {
             mService.pendingListenForNetwork(request.networkCapabilities, operation);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw convertServiceException(e);
         }
     }
 
@@ -3255,8 +3264,9 @@
         // capabilities, this request is guaranteed, at all times, to be
         // satisfied by the same network, if any, that satisfies the default
         // request, i.e., the system default network.
+        NetworkCapabilities nullCapabilities = null;
         CallbackHandler cbHandler = new CallbackHandler(handler);
-        sendRequestForNetwork(null, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
+        sendRequestForNetwork(nullCapabilities, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
     }
 
     /**
@@ -3280,25 +3290,44 @@
     }
 
     /**
-     * Unregisters callbacks about and possibly releases networks originating from
+     * Unregisters a {@code NetworkCallback} and possibly releases networks originating from
      * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and
      * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} calls.
      * If the given {@code NetworkCallback} had previously been used with
      * {@code #requestNetwork}, any networks that had been connected to only to satisfy that request
      * will be disconnected.
      *
+     * Notifications that would have triggered that {@code NetworkCallback} will immediately stop
+     * triggering it as soon as this call returns.
+     *
      * @param networkCallback The {@link NetworkCallback} used when making the request.
      */
     public void unregisterNetworkCallback(NetworkCallback networkCallback) {
-        if (networkCallback == null || networkCallback.networkRequest == null ||
-                networkCallback.networkRequest.requestId == REQUEST_ID_UNSET) {
-            throw new IllegalArgumentException("Invalid NetworkCallback");
-        }
-        try {
-            // CallbackHandler will release callback when receiving CALLBACK_RELEASED.
-            mService.releaseNetworkRequest(networkCallback.networkRequest);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        checkCallbackNotNull(networkCallback);
+        final List<NetworkRequest> reqs = new ArrayList<>();
+        // Find all requests associated to this callback and stop callback triggers immediately.
+        // Callback is reusable immediately. http://b/20701525, http://b/35921499.
+        synchronized (sCallbacks) {
+            Preconditions.checkArgument(networkCallback.networkRequest != null,
+                    "NetworkCallback was not registered");
+            Preconditions.checkArgument(networkCallback.networkRequest != ALREADY_UNREGISTERED,
+                    "NetworkCallback was already unregistered");
+            for (Map.Entry<NetworkRequest, NetworkCallback> e : sCallbacks.entrySet()) {
+                if (e.getValue() == networkCallback) {
+                    reqs.add(e.getKey());
+                }
+            }
+            // TODO: throw exception if callback was registered more than once (http://b/20701525).
+            for (NetworkRequest r : reqs) {
+                try {
+                    mService.releaseNetworkRequest(r);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                // Only remove mapping if rpc was successful.
+                sCallbacks.remove(r);
+            }
+            networkCallback.networkRequest = ALREADY_UNREGISTERED;
         }
     }
 
@@ -3312,6 +3341,7 @@
      *                  Cannot be null.
      */
     public void unregisterNetworkCallback(PendingIntent operation) {
+        checkPendingIntentNotNull(operation);
         releaseNetworkRequest(operation);
     }
 
@@ -3666,32 +3696,4 @@
             throw e.rethrowFromSystemServer();
         }
     }
-
-    /**
-     * A holder class for debug info (mapping CALLBACK values to field names). This is stored
-     * in a holder for two reasons:
-     * 1) The reflection necessary to establish the map can't be run at compile-time. Thus, this
-     *    code will make the enclosing class not compile-time initializeable, deferring its
-     *    initialization to zygote startup. This leads to dirty (but shared) memory.
-     *    As this is debug info, use a holder that isn't initialized by default. This way the map
-     *    will be created on demand, while ConnectivityManager can be compile-time initialized.
-     * 2) Static initialization is still preferred for its strong thread safety guarantees without
-     *    requiring a lock.
-     */
-    private static class NoPreloadHolder {
-        public static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(
-                new Class[]{ConnectivityManager.class}, new String[]{"CALLBACK_"});
-    }
-
-    static {
-        // When debug is enabled, aggressively initialize the holder by touching the field (which
-        // will guarantee static initialization).
-        if (CallbackHandler.DBG) {
-            Object dummy = NoPreloadHolder.sMagicDecoderRing;
-        }
-    }
-
-    private static final String whatToString(int what) {
-        return NoPreloadHolder.sMagicDecoderRing.get(what, Integer.toString(what));
-    }
 }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 6b2da61..27729dc 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -69,17 +69,18 @@
 
     boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
 
-    int tether(String iface);
+    int tether(String iface, String callerPkg);
 
-    int untether(String iface);
+    int untether(String iface, String callerPkg);
 
     int getLastTetherError(String iface);
 
     boolean isTetheringSupported();
 
-    void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi);
+    void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi,
+            String callerPkg);
 
-    void stopTethering(int type);
+    void stopTethering(int type, String callerPkg);
 
     String[] getTetherableIfaces();
 
@@ -95,7 +96,7 @@
 
     String[] getTetherableBluetoothRegexs();
 
-    int setUsbTethering(boolean enable);
+    int setUsbTethering(boolean enable, String callerPkg);
 
     void reportInetCondition(int networkType, int percentage);
 
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index bf7207c..0a5b9c1 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -23,6 +23,7 @@
 import com.android.internal.util.BitUtils;
 
 import java.util.Objects;
+import java.util.StringJoiner;
 
 /**
  * This class represents the capabilities of a network.  This is used both to specify
@@ -346,11 +347,6 @@
         return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
     }
 
-    private boolean equalsNetCapabilitiesImmutable(NetworkCapabilities that) {
-        return ((this.mNetworkCapabilities & ~MUTABLE_CAPABILITIES) ==
-                (that.mNetworkCapabilities & ~MUTABLE_CAPABILITIES));
-    }
-
     private boolean equalsNetCapabilitiesRequestable(NetworkCapabilities that) {
         return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
                 (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES));
@@ -418,10 +414,16 @@
      */
     public static final int TRANSPORT_WIFI_AWARE = 5;
 
+    /**
+     * Indicates this network uses a LoWPAN transport.
+     * @hide
+     */
+    public static final int TRANSPORT_LOWPAN = 6;
+
     /** @hide */
     public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
     /** @hide */
-    public static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
+    public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
 
     private static final String[] TRANSPORT_NAMES = {
         "CELLULAR",
@@ -429,7 +431,8 @@
         "BLUETOOTH",
         "ETHERNET",
         "VPN",
-        "WIFI_AWARE"
+        "WIFI_AWARE",
+        "LOWPAN"
     };
 
     /**
@@ -497,10 +500,12 @@
     private void combineTransportTypes(NetworkCapabilities nc) {
         this.mTransportTypes |= nc.mTransportTypes;
     }
+
     private boolean satisfiedByTransportTypes(NetworkCapabilities nc) {
         return ((this.mTransportTypes == 0) ||
                 ((this.mTransportTypes & nc.mTransportTypes) != 0));
     }
+
     /** @hide */
     public boolean equalsTransportTypes(NetworkCapabilities nc) {
         return (nc.mTransportTypes == this.mTransportTypes);
@@ -755,15 +760,43 @@
 
     /**
      * Checks that our immutable capabilities are the same as those of the given
-     * {@code NetworkCapabilities}.
+     * {@code NetworkCapabilities} and return a String describing any difference.
+     * The returned String is empty if there is no difference.
      *
      * @hide
      */
-    public boolean equalImmutableCapabilities(NetworkCapabilities nc) {
-        if (nc == null) return false;
-        return (equalsNetCapabilitiesImmutable(nc) &&
-                equalsTransportTypes(nc) &&
-                equalsSpecifier(nc));
+    public String describeImmutableDifferences(NetworkCapabilities that) {
+        if (that == null) {
+            return "other NetworkCapabilities was null";
+        }
+
+        StringJoiner joiner = new StringJoiner(", ");
+
+        // TODO: consider only enforcing that capabilities are not removed, allowing addition.
+        // Ignore NOT_METERED being added or removed as it is effectively dynamic. http://b/63326103
+        // TODO: properly support NOT_METERED as a mutable and requestable capability.
+        final long mask = ~MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_NOT_METERED);
+        long oldImmutableCapabilities = this.mNetworkCapabilities & mask;
+        long newImmutableCapabilities = that.mNetworkCapabilities & mask;
+        if (oldImmutableCapabilities != newImmutableCapabilities) {
+            String before = capabilityNamesOf(BitUtils.unpackBits(oldImmutableCapabilities));
+            String after = capabilityNamesOf(BitUtils.unpackBits(newImmutableCapabilities));
+            joiner.add(String.format("immutable capabilities changed: %s -> %s", before, after));
+        }
+
+        if (!equalsSpecifier(that)) {
+            NetworkSpecifier before = this.getNetworkSpecifier();
+            NetworkSpecifier after = that.getNetworkSpecifier();
+            joiner.add(String.format("specifier changed: %s -> %s", before, after));
+        }
+
+        if (!equalsTransportTypes(that)) {
+            String before = transportNamesOf(this.getTransportTypes());
+            String after = transportNamesOf(that.getTransportTypes());
+            joiner.add(String.format("transports changed: %s -> %s", before, after));
+        }
+
+        return joiner.toString();
     }
 
     /**
@@ -838,33 +871,15 @@
 
     @Override
     public String toString() {
+        // TODO: enumerate bits for transports and capabilities instead of creating arrays.
+        // TODO: use a StringBuilder instead of string concatenation.
         int[] types = getTransportTypes();
         String transports = (types.length > 0) ? " Transports: " + transportNamesOf(types) : "";
 
         types = getCapabilities();
         String capabilities = (types.length > 0 ? " Capabilities: " : "");
         for (int i = 0; i < types.length; ) {
-            switch (types[i]) {
-                case NET_CAPABILITY_MMS:            capabilities += "MMS"; break;
-                case NET_CAPABILITY_SUPL:           capabilities += "SUPL"; break;
-                case NET_CAPABILITY_DUN:            capabilities += "DUN"; break;
-                case NET_CAPABILITY_FOTA:           capabilities += "FOTA"; break;
-                case NET_CAPABILITY_IMS:            capabilities += "IMS"; break;
-                case NET_CAPABILITY_CBS:            capabilities += "CBS"; break;
-                case NET_CAPABILITY_WIFI_P2P:       capabilities += "WIFI_P2P"; break;
-                case NET_CAPABILITY_IA:             capabilities += "IA"; break;
-                case NET_CAPABILITY_RCS:            capabilities += "RCS"; break;
-                case NET_CAPABILITY_XCAP:           capabilities += "XCAP"; break;
-                case NET_CAPABILITY_EIMS:           capabilities += "EIMS"; break;
-                case NET_CAPABILITY_NOT_METERED:    capabilities += "NOT_METERED"; break;
-                case NET_CAPABILITY_INTERNET:       capabilities += "INTERNET"; break;
-                case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break;
-                case NET_CAPABILITY_TRUSTED:        capabilities += "TRUSTED"; break;
-                case NET_CAPABILITY_NOT_VPN:        capabilities += "NOT_VPN"; break;
-                case NET_CAPABILITY_VALIDATED:      capabilities += "VALIDATED"; break;
-                case NET_CAPABILITY_CAPTIVE_PORTAL: capabilities += "CAPTIVE_PORTAL"; break;
-                case NET_CAPABILITY_FOREGROUND:     capabilities += "FOREGROUND"; break;
-            }
+            capabilities += capabilityNameOf(types[i]);
             if (++i < types.length) capabilities += "&";
         }
 
@@ -884,15 +899,55 @@
     /**
      * @hide
      */
+    public static String capabilityNamesOf(int[] capabilities) {
+        StringJoiner joiner = new StringJoiner("|");
+        if (capabilities != null) {
+            for (int c : capabilities) {
+                joiner.add(capabilityNameOf(c));
+            }
+        }
+        return joiner.toString();
+    }
+
+    /**
+     * @hide
+     */
+    public static String capabilityNameOf(int capability) {
+        switch (capability) {
+            case NET_CAPABILITY_MMS:            return "MMS";
+            case NET_CAPABILITY_SUPL:           return "SUPL";
+            case NET_CAPABILITY_DUN:            return "DUN";
+            case NET_CAPABILITY_FOTA:           return "FOTA";
+            case NET_CAPABILITY_IMS:            return "IMS";
+            case NET_CAPABILITY_CBS:            return "CBS";
+            case NET_CAPABILITY_WIFI_P2P:       return "WIFI_P2P";
+            case NET_CAPABILITY_IA:             return "IA";
+            case NET_CAPABILITY_RCS:            return "RCS";
+            case NET_CAPABILITY_XCAP:           return "XCAP";
+            case NET_CAPABILITY_EIMS:           return "EIMS";
+            case NET_CAPABILITY_NOT_METERED:    return "NOT_METERED";
+            case NET_CAPABILITY_INTERNET:       return "INTERNET";
+            case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED";
+            case NET_CAPABILITY_TRUSTED:        return "TRUSTED";
+            case NET_CAPABILITY_NOT_VPN:        return "NOT_VPN";
+            case NET_CAPABILITY_VALIDATED:      return "VALIDATED";
+            case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
+            case NET_CAPABILITY_FOREGROUND:     return "FOREGROUND";
+            default:                            return Integer.toString(capability);
+        }
+    }
+
+    /**
+     * @hide
+     */
     public static String transportNamesOf(int[] types) {
-        if (types == null || types.length == 0) {
-            return "";
+        StringJoiner joiner = new StringJoiner("|");
+        if (types != null) {
+            for (int t : types) {
+                joiner.add(transportNameOf(t));
+            }
         }
-        StringBuilder transports = new StringBuilder();
-        for (int t : types) {
-            transports.append("|").append(transportNameOf(t));
-        }
-        return transports.substring(1);
+        return joiner.toString();
     }
 
     /**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4b12bc4..5c4826f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -29,6 +29,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.Nullable;
 import android.app.BroadcastOptions;
@@ -41,7 +42,6 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.PacketKeepalive;
@@ -62,6 +62,7 @@
 import android.net.NetworkMisc;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
 import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.Proxy;
@@ -85,10 +86,12 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -100,7 +103,6 @@
 import android.util.LocalLog;
 import android.util.LocalLog.ReadOnlyLocalLog;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -137,6 +139,7 @@
 import com.android.server.connectivity.PacManager;
 import com.android.server.connectivity.PermissionMonitor;
 import com.android.server.connectivity.Tethering;
+import com.android.server.connectivity.tethering.TetheringDependencies;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.net.LockdownVpnTracker;
@@ -177,6 +180,7 @@
 
     public static final String DIAG_ARG = "--diag";
     public static final String SHORT_ARG = "--short";
+    public static final String TETHERING_ARG = "tethering";
 
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
@@ -363,11 +367,6 @@
     private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
 
     /**
-     * used to specify whether a network should not be penalized when it becomes unvalidated.
-     */
-    private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
-
-    /**
      * used to ask the user to confirm a connection to an unvalidated network.
      * obj  = network
      */
@@ -384,6 +383,20 @@
      */
     private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
 
+    /**
+     * used to specify whether a network should not be penalized when it becomes unvalidated.
+     */
+    private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
+
+    /**
+     * used to trigger revalidation of a network.
+     */
+    private static final int EVENT_REVALIDATE_NETWORK = 36;
+
+    private static String eventName(int what) {
+        return sMagicDecoderRing.get(what, Integer.toString(what));
+    }
+
     /** Handler thread used for both of the handlers below. */
     @VisibleForTesting
     protected final HandlerThread mHandlerThread;
@@ -396,15 +409,9 @@
     private Intent mInitialBroadcast;
 
     private PowerManager.WakeLock mNetTransitionWakeLock;
-    private String mNetTransitionWakeLockCausedBy = "";
-    private int mNetTransitionWakeLockSerialNumber;
     private int mNetTransitionWakeLockTimeout;
     private final PowerManager.WakeLock mPendingIntentWakeLock;
 
-    // used in DBG mode to track inet condition reports
-    private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
-    private ArrayList mInetLog;
-
     // track the current default http proxy - tell the world if we get a new one (real change)
     private volatile ProxyInfo mDefaultProxy = null;
     private Object mProxyLock = new Object();
@@ -446,10 +453,12 @@
     private final LocalLog mNetworkRequestInfoLogs = new LocalLog(MAX_NETWORK_REQUEST_LOGS);
 
     // NetworkInfo blocked and unblocked String log entries
-    // TODO: consider reducing memory usage. Each log line is ~40 2B chars, for a total of ~8kB.
-    private static final int MAX_NETWORK_INFO_LOGS = 100;
+    private static final int MAX_NETWORK_INFO_LOGS = 40;
     private final LocalLog mNetworkInfoBlockingLogs = new LocalLog(MAX_NETWORK_INFO_LOGS);
 
+    private static final int MAX_WAKELOCK_LOGS = 20;
+    private final LocalLog mWakelockLogs = new LocalLog(MAX_WAKELOCK_LOGS);
+
     // Array of <Network,ReadOnlyLocalLogs> tracking network validation and results
     private static final int MAX_VALIDATION_LOGS = 10;
     private static class ValidationLog {
@@ -467,7 +476,7 @@
             new ArrayDeque<ValidationLog>(MAX_VALIDATION_LOGS);
 
     private void addValidationLogs(ReadOnlyLocalLog log, Network network, String networkExtraInfo) {
-        synchronized(mValidationLogs) {
+        synchronized (mValidationLogs) {
             while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
                 mValidationLogs.removeLast();
             }
@@ -785,8 +794,7 @@
         mTestMode = mSystemProperties.get("cm.test.mode").equals("true")
                 && mSystemProperties.get("ro.build.type").equals("eng");
 
-        mTethering = new Tethering(mContext, mNetd, statsService, mPolicyManager,
-                                   IoThread.get().getLooper(), new MockableSystemProperties());
+        mTethering = makeTethering();
 
         mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
 
@@ -809,10 +817,6 @@
             loge("Error registering observer :" + e);
         }
 
-        if (DBG) {
-            mInetLog = new ArrayList();
-        }
-
         mSettingsObserver = new SettingsObserver(mContext, mHandler);
         registerSettingsCallbacks();
 
@@ -840,6 +844,14 @@
         mMultinetworkPolicyTracker.start();
     }
 
+    private Tethering makeTethering() {
+        // TODO: Move other elements into @Overridden getters.
+        final TetheringDependencies deps = new TetheringDependencies();
+        return new Tethering(mContext, mNetd, mStatsService, mPolicyManager,
+                IoThread.get().getLooper(), new MockableSystemProperties(),
+                deps);
+    }
+
     private NetworkRequest createInternetRequestForTransport(
             int transportType, NetworkRequest.Type type) {
         NetworkCapabilities netCap = new NetworkCapabilities();
@@ -863,8 +875,8 @@
     }
 
     private void handleMobileDataAlwaysOn() {
-        final boolean enable = (Settings.Global.getInt(
-                mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1) == 1);
+        final boolean enable = toBool(Settings.Global.getInt(
+                mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1));
         final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null);
         if (enable == isEnabled) {
             return;  // Nothing to do.
@@ -1006,22 +1018,19 @@
         if (ni == null || !LOGD_BLOCKED_NETWORKINFO) {
             return;
         }
-        boolean removed = false;
-        boolean added = false;
+        final boolean blocked;
         synchronized (mBlockedAppUids) {
             if (ni.getDetailedState() == DetailedState.BLOCKED && mBlockedAppUids.add(uid)) {
-                added = true;
+                blocked = true;
             } else if (ni.isConnected() && mBlockedAppUids.remove(uid)) {
-                removed = true;
+                blocked = false;
+            } else {
+                return;
             }
         }
-        if (added) {
-            log("Returning blocked NetworkInfo to uid=" + uid);
-            mNetworkInfoBlockingLogs.log("BLOCKED " + uid);
-        } else if (removed) {
-            log("Returning unblocked NetworkInfo to uid=" + uid);
-            mNetworkInfoBlockingLogs.log("UNBLOCKED " + uid);
-        }
+        String action = blocked ? "BLOCKED" : "UNBLOCKED";
+        log(String.format("Returning %s NetworkInfo to uid=%d", action, uid));
+        mNetworkInfoBlockingLogs.log(action + " " + uid);
     }
 
     /**
@@ -1580,7 +1589,7 @@
     }
 
     private void sendStickyBroadcast(Intent intent) {
-        synchronized(this) {
+        synchronized (this) {
             if (!mSystemReady) {
                 mInitialBroadcast = new Intent(intent);
             }
@@ -1621,7 +1630,7 @@
     void systemReady() {
         loadGlobalProxy();
 
-        synchronized(this) {
+        synchronized (this) {
             mSystemReady = true;
             if (mInitialBroadcast != null) {
                 mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL);
@@ -1827,7 +1836,7 @@
 
     private boolean argsContain(String[] args, String target) {
         for (String arg : args) {
-            if (arg.equals(target)) return true;
+            if (target.equals(arg)) return true;
         }
         return false;
     }
@@ -1858,6 +1867,9 @@
         if (argsContain(args, DIAG_ARG)) {
             dumpNetworkDiagnostics(pw);
             return;
+        } else if (argsContain(args, TETHERING_ARG)) {
+            mTethering.dump(fd, pw, args);
+            return;
         }
 
         pw.print("NetworkFactories for:");
@@ -1911,16 +1923,6 @@
 
         mLegacyTypeTracker.dump(pw);
 
-        synchronized (this) {
-            pw.print("mNetTransitionWakeLock: currently " +
-                    (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held");
-            if (!TextUtils.isEmpty(mNetTransitionWakeLockCausedBy)) {
-                pw.println(", last requested for " + mNetTransitionWakeLockCausedBy);
-            } else {
-                pw.println(", last requested never");
-            }
-        }
-
         pw.println();
         mTethering.dump(fd, pw, args);
 
@@ -1930,17 +1932,6 @@
         pw.println();
         dumpAvoidBadWifiSettings(pw);
 
-        pw.println();
-        if (mInetLog != null && mInetLog.size() > 0) {
-            pw.println();
-            pw.println("Inet condition reports:");
-            pw.increaseIndent();
-            for(int i = 0; i < mInetLog.size(); i++) {
-                pw.println(mInetLog.get(i));
-            }
-            pw.decreaseIndent();
-        }
-
         if (argsContain(args, SHORT_ARG) == false) {
             pw.println();
             synchronized (mValidationLogs) {
@@ -1964,6 +1955,12 @@
             pw.increaseIndent();
             mNetworkInfoBlockingLogs.reverseDump(fd, pw, args);
             pw.decreaseIndent();
+
+            pw.println();
+            pw.println("NetTransition WakeLock activity (most recent first):");
+            pw.increaseIndent();
+            mWakelockLogs.reverseDump(fd, pw, args);
+            pw.decreaseIndent();
         }
     }
 
@@ -1972,8 +1969,7 @@
         final NetworkAgentInfo officialNai = getNetworkAgentInfoForNetwork(nai.network);
         if (officialNai != null && officialNai.equals(nai)) return true;
         if (officialNai != null || VDBG) {
-            final String msg = sMagicDecoderRing.get(what, Integer.toString(what));
-            loge(msg + " - isLiveNetworkAgent found mismatched netId: " + officialNai +
+            loge(eventName(what) + " - isLiveNetworkAgent found mismatched netId: " + officialNai +
                 " - " + nai);
         }
         return false;
@@ -2010,8 +2006,7 @@
             NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
             if (nai == null) {
                 if (VDBG) {
-                    final String what = sMagicDecoderRing.get(msg.what, Integer.toString(msg.what));
-                    log(String.format("%s from unknown NetworkAgent", what));
+                    log(String.format("%s from unknown NetworkAgent", eventName(msg.what)));
                 }
                 return;
             }
@@ -2122,7 +2117,7 @@
                 }
                 case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
                     final int netId = msg.arg2;
-                    final boolean visible = (msg.arg1 != 0);
+                    final boolean visible = toBool(msg.arg1);
                     final NetworkAgentInfo nai;
                     synchronized (mNetworkForNetId) {
                         nai = mNetworkForNetId.get(netId);
@@ -2280,6 +2275,10 @@
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
             mKeepaliveTracker.handleStopAllKeepalives(nai,
                     ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
+            for (String iface : nai.linkProperties.getAllInterfaceNames()) {
+                // Disable wakeup packet monitoring for each interface.
+                wakeupModifyInterface(iface, nai.networkCapabilities, false);
+            }
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
             mNetworkAgentInfos.remove(msg.replyTo);
             updateClat(null, nai.linkProperties, nai);
@@ -2301,7 +2300,7 @@
             if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
                 removeDataActivityTracking(nai);
                 notifyLockdownVpn(nai);
-                requestNetworkTransitionWakelock(nai.name());
+                ensureNetworkTransitionWakelock(nai.name());
             }
             mLegacyTypeTracker.remove(nai, wasDefault);
             rematchAllNetworksAndRequests(null, 0);
@@ -2448,25 +2447,32 @@
     }
 
     private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
-        if (mNetworkRequests.get(nri.request) != null && mNetworkForRequestId.get(
-                nri.request.requestId) == null) {
-            handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_UNAVAIL);
+        if (mNetworkRequests.get(nri.request) == null) {
+            return;
         }
+        if (mNetworkForRequestId.get(nri.request.requestId) != null) {
+            return;
+        }
+        if (VDBG || (DBG && nri.request.isRequest())) {
+            log("releasing " + nri.request + " (timeout)");
+        }
+        handleRemoveNetworkRequest(nri);
+        callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
     }
 
     private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
-        final NetworkRequestInfo nri = getNriForAppRequest(
-                request, callingUid, "release NetworkRequest");
-        if (nri != null) {
-            handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_RELEASED);
+        final NetworkRequestInfo nri =
+                getNriForAppRequest(request, callingUid, "release NetworkRequest");
+        if (nri == null) {
+            return;
         }
+        if (VDBG || (DBG && nri.request.isRequest())) {
+            log("releasing " + nri.request + " (release request)");
+        }
+        handleRemoveNetworkRequest(nri);
     }
 
-    private void handleRemoveNetworkRequest(final NetworkRequestInfo nri, final int whichCallback) {
-        final String logCallbackType = ConnectivityManager.getCallbackName(whichCallback);
-        if (VDBG || (DBG && nri.request.isRequest())) {
-            log("releasing " + nri.request + " (" + logCallbackType + ")");
-        }
+    private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) {
         nri.unlinkDeathRecipient();
         mNetworkRequests.remove(nri.request);
         synchronized (mUidToNetworkRequestCount) {
@@ -2562,14 +2568,13 @@
                 }
             }
         }
-        callCallbackForRequest(nri, null, whichCallback, 0);
     }
 
     @Override
     public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
         enforceConnectivityInternalPermission();
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
-                accept ? 1 : 0, always ? 1: 0, network));
+                encodeBool(accept), encodeBool(always), network));
     }
 
     @Override
@@ -2606,7 +2611,7 @@
 
         if (always) {
             nai.asyncChannel.sendMessage(
-                    NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0);
+                    NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, encodeBool(accept));
         }
 
         if (!accept) {
@@ -2771,25 +2776,7 @@
             switch (msg.what) {
                 case EVENT_EXPIRE_NET_TRANSITION_WAKELOCK:
                 case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: {
-                    String causedBy = null;
-                    synchronized (ConnectivityService.this) {
-                        if (msg.arg1 == mNetTransitionWakeLockSerialNumber &&
-                                mNetTransitionWakeLock.isHeld()) {
-                            mNetTransitionWakeLock.release();
-                            causedBy = mNetTransitionWakeLockCausedBy;
-                        } else {
-                            break;
-                        }
-                    }
-                    if (VDBG) {
-                        if (msg.what == EVENT_EXPIRE_NET_TRANSITION_WAKELOCK) {
-                            log("Failed to find a new network - expiring NetTransition Wakelock");
-                        } else {
-                            log("NetTransition Wakelock (" +
-                                    (causedBy == null ? "unknown" : causedBy) +
-                                    " cleared because we found a replacement network");
-                        }
-                    }
+                    handleReleaseNetworkTransitionWakelock(msg.what);
                     break;
                 }
                 case EVENT_APPLY_GLOBAL_HTTP_PROXY: {
@@ -2836,7 +2823,8 @@
                     break;
                 }
                 case EVENT_SET_ACCEPT_UNVALIDATED: {
-                    handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0);
+                    Network network = (Network) msg.obj;
+                    handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
                     break;
                 }
                 case EVENT_SET_AVOID_UNVALIDATED: {
@@ -2870,14 +2858,18 @@
                     }
                     break;
                 }
+                case EVENT_REVALIDATE_NETWORK: {
+                    handleReportNetworkConnectivity((Network) msg.obj, msg.arg1, toBool(msg.arg2));
+                    break;
+                }
             }
         }
     }
 
     // javadoc from interface
     @Override
-    public int tether(String iface) {
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+    public int tether(String iface, String callerPkg) {
+        ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
         if (isTetheringSupported()) {
             final int status = mTethering.tether(iface);
             return status;
@@ -2888,8 +2880,8 @@
 
     // javadoc from interface
     @Override
-    public int untether(String iface) {
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+    public int untether(String iface, String callerPkg) {
+        ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
 
         if (isTetheringSupported()) {
             final int status = mTethering.untether(iface);
@@ -2943,8 +2935,8 @@
     }
 
     @Override
-    public int setUsbTethering(boolean enable) {
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+    public int setUsbTethering(boolean enable, String callerPkg) {
+        ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
         if (isTetheringSupported()) {
             return mTethering.setUsbTethering(enable);
         } else {
@@ -2984,9 +2976,10 @@
     @Override
     public boolean isTetheringSupported() {
         enforceTetherAccessPermission();
-        int defaultVal = (mSystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
-        boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
+        int defaultVal = encodeBool(!mSystemProperties.get("ro.tether.denied").equals("true"));
+        boolean tetherSupported = toBool(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.TETHER_SUPPORTED, defaultVal));
+        boolean tetherEnabledInSettings = tetherSupported
                 && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
 
         // Elevate to system UID to avoid caller requiring MANAGE_USERS permission.
@@ -2998,13 +2991,13 @@
             Binder.restoreCallingIdentity(token);
         }
 
-        return tetherEnabledInSettings && adminUser &&
-               mTethering.hasTetherableConfiguration();
+        return tetherEnabledInSettings && adminUser && mTethering.hasTetherableConfiguration();
     }
 
     @Override
-    public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+    public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi,
+            String callerPkg) {
+        ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
         if (!isTetheringSupported()) {
             receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
             return;
@@ -3013,8 +3006,8 @@
     }
 
     @Override
-    public void stopTethering(int type) {
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+    public void stopTethering(int type, String callerPkg) {
+        ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
         mTethering.stopTethering(type);
     }
 
@@ -3022,18 +3015,46 @@
     // This will automatically be cleared after X seconds or a new default network
     // becomes CONNECTED, whichever happens first.  The timer is started by the
     // first caller and not restarted by subsequent callers.
-    private void requestNetworkTransitionWakelock(String forWhom) {
-        int serialNum = 0;
+    private void ensureNetworkTransitionWakelock(String forWhom) {
         synchronized (this) {
-            if (mNetTransitionWakeLock.isHeld()) return;
-            serialNum = ++mNetTransitionWakeLockSerialNumber;
+            if (mNetTransitionWakeLock.isHeld()) {
+                return;
+            }
             mNetTransitionWakeLock.acquire();
-            mNetTransitionWakeLockCausedBy = forWhom;
         }
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                EVENT_EXPIRE_NET_TRANSITION_WAKELOCK, serialNum, 0),
-                mNetTransitionWakeLockTimeout);
-        return;
+        mWakelockLogs.log("ACQUIRE for " + forWhom);
+        Message msg = mHandler.obtainMessage(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK);
+        mHandler.sendMessageDelayed(msg, mNetTransitionWakeLockTimeout);
+    }
+
+    // Called when we gain a new default network to release the network transition wakelock in a
+    // second, to allow a grace period for apps to reconnect over the new network. Pending expiry
+    // message is cancelled.
+    private void scheduleReleaseNetworkTransitionWakelock() {
+        synchronized (this) {
+            if (!mNetTransitionWakeLock.isHeld()) {
+                return; // expiry message released the lock first.
+            }
+        }
+        // Cancel self timeout on wakelock hold.
+        mHandler.removeMessages(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK);
+        Message msg = mHandler.obtainMessage(EVENT_CLEAR_NET_TRANSITION_WAKELOCK);
+        mHandler.sendMessageDelayed(msg, 1000);
+    }
+
+    // Called when either message of ensureNetworkTransitionWakelock or
+    // scheduleReleaseNetworkTransitionWakelock is processed.
+    private void handleReleaseNetworkTransitionWakelock(int eventId) {
+        String event = eventName(eventId);
+        synchronized (this) {
+            if (!mNetTransitionWakeLock.isHeld()) {
+                mWakelockLogs.log(String.format("RELEASE: already released (%s)", event));
+                Slog.w(TAG, "expected Net Transition WakeLock to be held");
+                return;
+            }
+            mNetTransitionWakeLock.release();
+        }
+        mWakelockLogs.log(String.format("RELEASE (%s)", event));
     }
 
     // 100 percent is full good, 0 is full bad.
@@ -3048,8 +3069,14 @@
     public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
         enforceAccessPermission();
         enforceInternetPermission();
+        final int uid = Binder.getCallingUid();
+        final int connectivityInfo = encodeBool(hasConnectivity);
+        mHandler.sendMessage(
+                mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network));
+    }
 
-        // TODO: execute this logic on ConnectivityService handler.
+    private void handleReportNetworkConnectivity(
+            Network network, int uid, boolean hasConnectivity) {
         final NetworkAgentInfo nai;
         if (network == null) {
             nai = getDefaultNetwork();
@@ -3064,10 +3091,9 @@
         if (hasConnectivity == nai.lastValidated) {
             return;
         }
-        final int uid = Binder.getCallingUid();
         if (DBG) {
-            log("reportNetworkConnectivity(" + nai.network.netId + ", " + hasConnectivity +
-                    ") by " + uid);
+            int netid = nai.network.netId;
+            log("reportNetworkConnectivity(" + netid + ", " + hasConnectivity + ") by " + uid);
         }
         // Validating a network that has not yet connected could result in a call to
         // rematchNetworkAndRequests() which is not meant to work on such networks.
@@ -3354,13 +3380,6 @@
         Slog.e(TAG, s, t);
     }
 
-    private static <T> T checkNotNull(T value, String message) {
-        if (value == null) {
-            throw new NullPointerException(message);
-        }
-        return value;
-    }
-
     /**
      * Prepare for a VPN application.
      * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
@@ -3381,7 +3400,7 @@
         enforceCrossUserPermission(userId);
         throwIfLockdownEnabled();
 
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             Vpn vpn = mVpns.get(userId);
             if (vpn != null) {
                 return vpn.prepare(oldPackage, newPackage);
@@ -3408,7 +3427,7 @@
     public void setVpnPackageAuthorization(String packageName, int userId, boolean authorized) {
         enforceCrossUserPermission(userId);
 
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             Vpn vpn = mVpns.get(userId);
             if (vpn != null) {
                 vpn.setPackageAuthorization(packageName, authorized);
@@ -3427,7 +3446,7 @@
     public ParcelFileDescriptor establishVpn(VpnConfig config) {
         throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             return mVpns.get(user).establish(config);
         }
     }
@@ -3444,7 +3463,7 @@
             throw new IllegalStateException("Missing active network connection");
         }
         int user = UserHandle.getUserId(Binder.getCallingUid());
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
         }
     }
@@ -3458,7 +3477,7 @@
     public LegacyVpnInfo getLegacyVpnInfo(int userId) {
         enforceCrossUserPermission(userId);
 
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             return mVpns.get(userId).getLegacyVpnInfo();
         }
     }
@@ -3474,7 +3493,7 @@
             return new VpnInfo[0];
         }
 
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             List<VpnInfo> infoList = new ArrayList<>();
             for (int i = 0; i < mVpns.size(); i++) {
                 VpnInfo info = createVpnInfo(mVpns.valueAt(i));
@@ -3522,7 +3541,7 @@
     @Override
     public VpnConfig getVpnConfig(int userId) {
         enforceCrossUserPermission(userId);
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             Vpn vpn = mVpns.get(userId);
             if (vpn != null) {
                 return vpn.getVpnConfig();
@@ -3556,7 +3575,7 @@
                 return true;
             }
             int user = UserHandle.getUserId(Binder.getCallingUid());
-            synchronized(mVpns) {
+            synchronized (mVpns) {
                 Vpn vpn = mVpns.get(user);
                 if (vpn == null) {
                     Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
@@ -3583,17 +3602,9 @@
             existing.shutdown();
         }
 
-        try {
-            if (tracker != null) {
-                mNetd.setFirewallEnabled(true);
-                mNetd.setFirewallInterfaceRule("lo", true);
-                mLockdownTracker = tracker;
-                mLockdownTracker.init();
-            } else {
-                mNetd.setFirewallEnabled(false);
-            }
-        } catch (RemoteException e) {
-            // ignored; NMS lives inside system_server
+        if (tracker != null) {
+            mLockdownTracker = tracker;
+            mLockdownTracker.init();
         }
     }
 
@@ -3647,8 +3658,6 @@
                 vpn.setAlwaysOnPackage(null, false);
                 return false;
             }
-
-            vpn.saveAlwaysOnPackage();
         }
         return true;
     }
@@ -3791,7 +3800,7 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             final ContentResolver cr = mContext.getContentResolver();
-            Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0);
+            Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, encodeBool(enable));
             Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
             intent.putExtra("state", enable);
             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
@@ -3801,7 +3810,7 @@
     }
 
     private void onUserStart(int userId) {
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             Vpn userVpn = mVpns.get(userId);
             if (userVpn != null) {
                 loge("Starting user already has a VPN");
@@ -3809,15 +3818,6 @@
             }
             userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
             mVpns.put(userId, userVpn);
-
-            final ContentResolver cr = mContext.getContentResolver();
-            String alwaysOnPackage = Settings.Secure.getStringForUser(cr,
-                    Settings.Secure.ALWAYS_ON_VPN_APP, userId);
-            final boolean alwaysOnLockdown = Settings.Secure.getIntForUser(cr,
-                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, /* default */ 0, userId) != 0;
-            if (alwaysOnPackage != null) {
-                userVpn.setAlwaysOnPackage(alwaysOnPackage, alwaysOnLockdown);
-            }
         }
         if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
             updateLockdownVpn();
@@ -3825,7 +3825,7 @@
     }
 
     private void onUserStop(int userId) {
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             Vpn userVpn = mVpns.get(userId);
             if (userVpn == null) {
                 loge("Stopped user has no VPN");
@@ -3837,7 +3837,7 @@
     }
 
     private void onUserAdded(int userId) {
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             final int vpnsSize = mVpns.size();
             for (int i = 0; i < vpnsSize; i++) {
                 Vpn vpn = mVpns.valueAt(i);
@@ -3847,7 +3847,7 @@
     }
 
     private void onUserRemoved(int userId) {
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             final int vpnsSize = mVpns.size();
             for (int i = 0; i < vpnsSize; i++) {
                 Vpn vpn = mVpns.valueAt(i);
@@ -3971,7 +3971,8 @@
             synchronized (mUidToNetworkRequestCount) {
                 int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1;
                 if (networkRequests >= MAX_NETWORK_REQUESTS_PER_UID) {
-                    throw new IllegalArgumentException("Too many NetworkRequests filed");
+                    throw new ServiceSpecificException(
+                            ConnectivityManager.Errors.TOO_MANY_REQUESTS);
                 }
                 mUidToNetworkRequestCount.put(mUid, networkRequests);
             }
@@ -4037,6 +4038,18 @@
                 0, 0, thresholds);
     }
 
+    private void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
+        if (nc == null) {
+            return;
+        }
+        NetworkSpecifier ns = nc.getNetworkSpecifier();
+        if (ns == null) {
+            return;
+        }
+        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns);
+        ns.assertValidFromUid(Binder.getCallingUid());
+    }
+
     @Override
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
             Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
@@ -4062,9 +4075,7 @@
         if (timeoutMs < 0) {
             throw new IllegalArgumentException("Bad timeout specified");
         }
-
-        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
-                networkCapabilities.getNetworkSpecifier());
+        ensureValidNetworkSpecifier(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), type);
@@ -4132,9 +4143,7 @@
         enforceNetworkRequestPermissions(networkCapabilities);
         enforceMeteredApnPolicy(networkCapabilities);
         ensureRequestableCapabilities(networkCapabilities);
-
-        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
-                networkCapabilities.getNetworkSpecifier());
+        ensureValidNetworkSpecifier(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -4196,9 +4205,7 @@
             // can't request networks.
             nc.addCapability(NET_CAPABILITY_FOREGROUND);
         }
-
-        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
-                networkCapabilities.getNetworkSpecifier());
+        ensureValidNetworkSpecifier(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
@@ -4216,9 +4223,7 @@
         if (!hasWifiNetworkListenPermission(networkCapabilities)) {
             enforceAccessPermission();
         }
-
-        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
-                networkCapabilities.getNetworkSpecifier());
+        ensureValidNetworkSpecifier(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(
                 new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId(),
@@ -4355,7 +4360,7 @@
             networkAgent.clatd.fixupLinkProperties(oldLp);
         }
 
-        updateInterfaces(newLp, oldLp, netId);
+        updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
         updateMtu(newLp, oldLp);
         // TODO - figure out what to do for clat
 //        for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -4393,7 +4398,39 @@
         }
     }
 
-    private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId) {
+    private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
+        // Marks are only available on WiFi interaces. Checking for
+        // marks on unsupported interfaces is harmless.
+        if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+            return;
+        }
+
+        int mark = mContext.getResources().getInteger(
+            com.android.internal.R.integer.config_networkWakeupPacketMark);
+        int mask = mContext.getResources().getInteger(
+            com.android.internal.R.integer.config_networkWakeupPacketMask);
+
+        // Mask/mark of zero will not detect anything interesting.
+        // Don't install rules unless both values are nonzero.
+        if (mark == 0 || mask == 0) {
+            return;
+        }
+
+        final String prefix = "iface:" + iface;
+        try {
+            if (add) {
+                mNetd.getNetdService().wakeupAddInterface(iface, prefix, mark, mask);
+            } else {
+                mNetd.getNetdService().wakeupDelInterface(iface, prefix, mark, mask);
+            }
+        } catch (Exception e) {
+            loge("Exception modifying wakeup packet monitoring: " + e);
+        }
+
+    }
+
+    private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId,
+                                  NetworkCapabilities caps) {
         CompareResult<String> interfaceDiff = new CompareResult<String>();
         if (oldLp != null) {
             interfaceDiff = oldLp.compareAllInterfaceNames(newLp);
@@ -4404,6 +4441,7 @@
             try {
                 if (DBG) log("Adding iface " + iface + " to network " + netId);
                 mNetd.addInterfaceToNetwork(iface, netId);
+                wakeupModifyInterface(iface, caps, true);
             } catch (Exception e) {
                 loge("Exception adding interface: " + e);
             }
@@ -4411,6 +4449,7 @@
         for (String iface : interfaceDiff.removed) {
             try {
                 if (DBG) log("Removing iface " + iface + " from network " + netId);
+                wakeupModifyInterface(iface, caps, false);
                 mNetd.removeInterfaceFromNetwork(iface, netId);
             } catch (Exception e) {
                 loge("Exception removing interface: " + e);
@@ -4532,10 +4571,12 @@
      */
     private void updateCapabilities(
             int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
-        if (nai.everConnected && !nai.networkCapabilities.equalImmutableCapabilities(
-                networkCapabilities)) {
-            Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: "
-                    + nai.networkCapabilities + " -> " + networkCapabilities);
+        // Sanity check: a NetworkAgent should not change its static capabilities or parameters.
+        if (nai.everConnected) {
+            String diff = nai.networkCapabilities.describeImmutableDifferences(networkCapabilities);
+            if (!TextUtils.isEmpty(diff)) {
+                Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities:" + diff);
+            }
         }
 
         // Don't modify caller's NetworkCapabilities.
@@ -4639,16 +4680,17 @@
         releasePendingNetworkRequestWithDelay(pendingIntent);
     }
 
-    private void callCallbackForRequest(NetworkRequestInfo nri,
+    private static void callCallbackForRequest(NetworkRequestInfo nri,
             NetworkAgentInfo networkAgent, int notificationType, int arg1) {
-        if (nri.messenger == null) return;  // Default request has no msgr
+        if (nri.messenger == null) {
+            return;  // Default request has no msgr
+        }
         Bundle bundle = new Bundle();
-        bundle.putParcelable(NetworkRequest.class.getSimpleName(),
-                new NetworkRequest(nri.request));
+        // TODO: check if defensive copies of data is needed.
+        putParcelable(bundle, new NetworkRequest(nri.request));
         Message msg = Message.obtain();
-        if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL &&
-                notificationType != ConnectivityManager.CALLBACK_RELEASED) {
-            bundle.putParcelable(Network.class.getSimpleName(), networkAgent.network);
+        if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
+            putParcelable(bundle, networkAgent.network);
         }
         switch (notificationType) {
             case ConnectivityManager.CALLBACK_LOSING: {
@@ -4656,13 +4698,11 @@
                 break;
             }
             case ConnectivityManager.CALLBACK_CAP_CHANGED: {
-                bundle.putParcelable(NetworkCapabilities.class.getSimpleName(),
-                        new NetworkCapabilities(networkAgent.networkCapabilities));
+                putParcelable(bundle, new NetworkCapabilities(networkAgent.networkCapabilities));
                 break;
             }
             case ConnectivityManager.CALLBACK_IP_CHANGED: {
-                bundle.putParcelable(LinkProperties.class.getSimpleName(),
-                        new LinkProperties(networkAgent.linkProperties));
+                putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
                 break;
             }
         }
@@ -4670,8 +4710,8 @@
         msg.setData(bundle);
         try {
             if (VDBG) {
-                log("sending notification " + notifyTypeToName(notificationType) +
-                        " for " + nri.request);
+                String notification = ConnectivityManager.getCallbackName(notificationType);
+                log("sending notification " + notification + " for " + nri.request);
             }
             nri.messenger.send(msg);
         } catch (RemoteException e) {
@@ -4680,6 +4720,10 @@
         }
     }
 
+    private static <T extends Parcelable> void putParcelable(Bundle bundle, T t) {
+        bundle.putParcelable(t.getClass().getSimpleName(), t);
+    }
+
     private void teardownUnneededNetwork(NetworkAgentInfo nai) {
         if (nai.numRequestNetworkRequests() != 0) {
             for (int i = 0; i < nai.numNetworkRequests(); i++) {
@@ -4893,17 +4937,8 @@
             makeDefault(newNetwork);
             // Log 0 -> X and Y -> X default network transitions, where X is the new default.
             logDefaultNetworkEvent(newNetwork, oldDefaultNetwork);
-            synchronized (ConnectivityService.this) {
-                // have a new default network, release the transition wakelock in
-                // a second if it's held.  The second pause is to allow apps
-                // to reconnect over the new network
-                if (mNetTransitionWakeLock.isHeld()) {
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                            EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
-                            mNetTransitionWakeLockSerialNumber, 0),
-                            1000);
-                }
-            }
+            // Have a new default network, release the transition wakelock in
+            scheduleReleaseNetworkTransitionWakelock();
         }
 
         if (!newNetwork.networkCapabilities.equalRequestableCapabilities(nc)) {
@@ -5290,7 +5325,10 @@
     }
 
     protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType, int arg1) {
-        if (VDBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name());
+        if (VDBG) {
+            String notification = ConnectivityManager.getCallbackName(notifyType);
+            log("notifyType " + notification + " for " + networkAgent.name());
+        }
         for (int i = 0; i < networkAgent.numNetworkRequests(); i++) {
             NetworkRequest nr = networkAgent.requestAt(i);
             NetworkRequestInfo nri = mNetworkRequests.get(nr);
@@ -5309,20 +5347,6 @@
         notifyNetworkCallbacks(networkAgent, notifyType, 0);
     }
 
-    private String notifyTypeToName(int notifyType) {
-        switch (notifyType) {
-            case ConnectivityManager.CALLBACK_PRECHECK:    return "PRECHECK";
-            case ConnectivityManager.CALLBACK_AVAILABLE:   return "AVAILABLE";
-            case ConnectivityManager.CALLBACK_LOSING:      return "LOSING";
-            case ConnectivityManager.CALLBACK_LOST:        return "LOST";
-            case ConnectivityManager.CALLBACK_UNAVAIL:     return "UNAVAILABLE";
-            case ConnectivityManager.CALLBACK_CAP_CHANGED: return "CAP_CHANGED";
-            case ConnectivityManager.CALLBACK_IP_CHANGED:  return "IP_CHANGED";
-            case ConnectivityManager.CALLBACK_RELEASED:    return "RELEASED";
-        }
-        return "UNKNOWN";
-    }
-
     /**
      * Notify NetworkStatsService that the set of active ifaces has changed, or that one of the
      * properties tracked by NetworkStatsService on an active iface has changed.
@@ -5402,8 +5426,9 @@
 
         if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
             // Untether
+            String pkgName = mContext.getOpPackageName();
             for (String tether : getTetheredIfaces()) {
-                untether(tether);
+                untether(tether, pkgName);
             }
         }
 
@@ -5488,4 +5513,12 @@
     private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
         mMetricsLog.log(new NetworkEvent(nai.network.netId, evtype));
     }
+
+    private static boolean toBool(int encodedBoolean) {
+        return encodedBoolean != 0; // Only 0 means false.
+    }
+
+    private static int encodeBool(boolean b) {
+        return b ? 1 : 0;
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 703e50a..0d935db 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -142,6 +142,18 @@
             extraInfo = null;
         }
 
+        // Clear any previous notification with lower priority, otherwise return. http://b/63676954.
+        // A new SIGN_IN notification with a new intent should override any existing one.
+        final int previousEventId = mNotificationTypeMap.get(id);
+        final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
+        if (priority(previousNotifyType) > priority(notifyType)) {
+            Slog.d(TAG, String.format(
+                    "ignoring notification %s for network %s with existing notification %s",
+                    notifyType, id, previousNotifyType));
+            return;
+        }
+        clearNotification(id);
+
         if (DBG) {
             Slog.d(TAG, String.format(
                     "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s",
@@ -274,4 +286,22 @@
         NotificationType t = NotificationType.getFromId(eventId);
         return (t != null) ? t.name() : "UNKNOWN";
     }
+
+    private static int priority(NotificationType t) {
+        if (t == null) {
+            return 0;
+        }
+        switch (t) {
+            case SIGN_IN:
+                return 4;
+            case NO_INTERNET:
+                return 3;
+            case NETWORK_SWITCH:
+                return 2;
+            case LOST_INTERNET:
+                return 1;
+            default:
+                return 0;
+        }
+    }
 }
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index b984bbf..cc792cc 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -36,21 +36,49 @@
 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.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.app.PendingIntent;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
-
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.content.pm.ApplicationInfo;
+import android.os.Build.VERSION_CODES;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import org.junit.runner.RunWith;
+import org.junit.Before;
 import org.junit.Test;
-
-
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class ConnectivityManagerTest {
+
+    @Mock Context mCtx;
+    @Mock IConnectivityManager mService;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
     static NetworkCapabilities verifyNetworkCapabilities(
             int legacyType, int transportType, int... capabilities) {
         final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
@@ -173,4 +201,161 @@
         verifyUnrestrictedNetworkCapabilities(
                 ConnectivityManager.TYPE_ETHERNET, TRANSPORT_ETHERNET);
     }
+
+    @Test
+    public void testCallbackRelease() throws Exception {
+        ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+        NetworkRequest request = makeRequest(1);
+        NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class);
+        Handler handler = new Handler(Looper.getMainLooper());
+        ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+
+        // register callback
+        when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+                .thenReturn(request);
+        manager.requestNetwork(request, callback, handler);
+
+        // callback triggers
+        captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_AVAILABLE));
+        verify(callback, timeout(500).times(1)).onAvailable(any());
+
+        // unregister callback
+        manager.unregisterNetworkCallback(callback);
+        verify(mService, times(1)).releaseNetworkRequest(request);
+
+        // callback does not trigger anymore.
+        captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_LOSING));
+        verify(callback, timeout(500).times(0)).onLosing(any(), anyInt());
+    }
+
+    @Test
+    public void testCallbackRecycling() throws Exception {
+        ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+        NetworkRequest req1 = makeRequest(1);
+        NetworkRequest req2 = makeRequest(2);
+        NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class);
+        Handler handler = new Handler(Looper.getMainLooper());
+        ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+
+        // register callback
+        when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+                .thenReturn(req1);
+        manager.requestNetwork(req1, callback, handler);
+
+        // callback triggers
+        captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_AVAILABLE));
+        verify(callback, timeout(100).times(1)).onAvailable(any());
+
+        // unregister callback
+        manager.unregisterNetworkCallback(callback);
+        verify(mService, times(1)).releaseNetworkRequest(req1);
+
+        // callback does not trigger anymore.
+        captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_LOSING));
+        verify(callback, timeout(100).times(0)).onLosing(any(), anyInt());
+
+        // callback can be registered again
+        when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+                .thenReturn(req2);
+        manager.requestNetwork(req2, callback, handler);
+
+        // callback triggers
+        captor.getValue().send(makeMessage(req2, ConnectivityManager.CALLBACK_LOST));
+        verify(callback, timeout(100).times(1)).onLost(any());
+
+        // unregister callback
+        manager.unregisterNetworkCallback(callback);
+        verify(mService, times(1)).releaseNetworkRequest(req2);
+    }
+
+    // TODO: turn on this test when request  callback 1:1 mapping is enforced
+    //@Test
+    private void noDoubleCallbackRegistration() throws Exception {
+        ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+        NetworkRequest request = makeRequest(1);
+        NetworkCallback callback = new ConnectivityManager.NetworkCallback();
+        ApplicationInfo info = new ApplicationInfo();
+        // TODO: update version when starting to enforce 1:1 mapping
+        info.targetSdkVersion = VERSION_CODES.N_MR1 + 1;
+
+        when(mCtx.getApplicationInfo()).thenReturn(info);
+        when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt())).thenReturn(request);
+
+        Handler handler = new Handler(Looper.getMainLooper());
+        manager.requestNetwork(request, callback, handler);
+
+        // callback is already registered, reregistration should fail.
+        Class<IllegalArgumentException> wantException = IllegalArgumentException.class;
+        expectThrowable(() -> manager.requestNetwork(request, callback), wantException);
+
+        manager.unregisterNetworkCallback(callback);
+        verify(mService, times(1)).releaseNetworkRequest(request);
+
+        // unregistering the callback should make it registrable again.
+        manager.requestNetwork(request, callback);
+    }
+
+    @Test
+    public void testArgumentValidation() throws Exception {
+        ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+        NetworkRequest request = mock(NetworkRequest.class);
+        NetworkCallback callback = mock(NetworkCallback.class);
+        Handler handler = mock(Handler.class);
+        NetworkCallback nullCallback = null;
+        PendingIntent nullIntent = null;
+
+        mustFail(() -> { manager.requestNetwork(null, callback); });
+        mustFail(() -> { manager.requestNetwork(request, nullCallback); });
+        mustFail(() -> { manager.requestNetwork(request, callback, null); });
+        mustFail(() -> { manager.requestNetwork(request, callback, -1); });
+        mustFail(() -> { manager.requestNetwork(request, nullIntent); });
+
+        mustFail(() -> { manager.registerNetworkCallback(null, callback, handler); });
+        mustFail(() -> { manager.registerNetworkCallback(request, null, handler); });
+        mustFail(() -> { manager.registerNetworkCallback(request, callback, null); });
+        mustFail(() -> { manager.registerNetworkCallback(request, nullIntent); });
+
+        mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); });
+        mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); });
+
+        mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); });
+        mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); });
+        mustFail(() -> { manager.releaseNetworkRequest(nullIntent); });
+    }
+
+    static void mustFail(Runnable fn) {
+        try {
+            fn.run();
+            fail();
+        } catch (Exception expected) {
+        }
+    }
+
+    static Message makeMessage(NetworkRequest req, int messageType) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(NetworkRequest.class.getSimpleName(), req);
+        Message msg = Message.obtain();
+        msg.what = messageType;
+        msg.setData(bundle);
+        return msg;
+    }
+
+    static NetworkRequest makeRequest(int requestId) {
+        NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+        return new NetworkRequest(request.networkCapabilities, ConnectivityManager.TYPE_NONE,
+                requestId, NetworkRequest.Type.NONE);
+    }
+
+    static void expectThrowable(Runnable block, Class<? extends Throwable> throwableType) {
+        try {
+            block.run();
+        } catch (Throwable t) {
+            if (t.getClass().equals(throwableType)) {
+                return;
+            }
+            fail("expected exception of type " + throwableType + ", but was " + t.getClass());
+        }
+        fail("expected exception of type " + throwableType);
+    }
 }
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java
index e3b06c8..7346f9f 100644
--- a/tests/net/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java
@@ -21,10 +21,14 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 
@@ -114,4 +118,45 @@
         assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
     }
 
+    @Test
+    public void testDescribeImmutableDifferences() {
+        NetworkCapabilities nc1;
+        NetworkCapabilities nc2;
+
+        // Transports changing
+        nc1 = new NetworkCapabilities().addTransportType(TRANSPORT_CELLULAR);
+        nc2 = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI);
+        assertNotEquals("", nc1.describeImmutableDifferences(nc2));
+        assertEquals("", nc1.describeImmutableDifferences(nc1));
+
+        // Mutable capability changing
+        nc1 = new NetworkCapabilities().addCapability(NET_CAPABILITY_VALIDATED);
+        nc2 = new NetworkCapabilities();
+        assertEquals("", nc1.describeImmutableDifferences(nc2));
+        assertEquals("", nc1.describeImmutableDifferences(nc1));
+
+        // NOT_METERED changing (http://b/63326103)
+        nc1 = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_NOT_METERED)
+                .addCapability(NET_CAPABILITY_INTERNET);
+        nc2 = new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET);
+        assertEquals("", nc1.describeImmutableDifferences(nc2));
+        assertEquals("", nc1.describeImmutableDifferences(nc1));
+
+        // Immutable capability changing
+        nc1 = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        nc2 = new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET);
+        assertNotEquals("", nc1.describeImmutableDifferences(nc2));
+        assertEquals("", nc1.describeImmutableDifferences(nc1));
+
+        // Specifier changing
+        nc1 = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI);
+        nc2 = new NetworkCapabilities()
+                .addTransportType(TRANSPORT_WIFI)
+                .setNetworkSpecifier(new StringNetworkSpecifier("specs"));
+        assertNotEquals("", nc1.describeImmutableDifferences(nc2));
+        assertEquals("", nc1.describeImmutableDifferences(nc1));
+    }
 }
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
new file mode 100644
index 0000000..adf6998
--- /dev/null
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2017 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.nsd;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.HandlerThread;
+import android.os.Handler;
+import android.os.Looper;
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.os.Message;
+import android.os.Messenger;
+import com.android.internal.util.AsyncChannel;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdManagerTest {
+
+    static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+
+    @Mock Context mContext;
+    @Mock INsdManager mService;
+    MockServiceHandler mServiceHandler;
+
+    long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mServiceHandler = spy(MockServiceHandler.create(mContext));
+        when(mService.getMessenger()).thenReturn(new Messenger(mServiceHandler));
+    }
+
+    @Test
+    public void testResolveService() {
+        NsdManager manager = makeManager();
+
+        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+        NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
+        NsdManager.ResolveListener listener = mock(NsdManager.ResolveListener.class);
+
+        manager.resolveService(request, listener);
+        int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+        int err = 33;
+        sendResponse(NsdManager.RESOLVE_SERVICE_FAILED, err, key1, null);
+        verify(listener, timeout(mTimeoutMs).times(1)).onResolveFailed(request, err);
+
+        manager.resolveService(request, listener);
+        int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+        sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
+        verify(listener, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+    }
+
+    @Test
+    public void testParallelResolveService() {
+        NsdManager manager = makeManager();
+
+        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+        NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
+
+        NsdManager.ResolveListener listener1 = mock(NsdManager.ResolveListener.class);
+        NsdManager.ResolveListener listener2 = mock(NsdManager.ResolveListener.class);
+
+        manager.resolveService(request, listener1);
+        int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+
+        manager.resolveService(request, listener2);
+        int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+
+        sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
+        sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key1, reply);
+
+        verify(listener1, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+        verify(listener2, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+    }
+
+    @Test
+    public void testRegisterService() {
+        NsdManager manager = makeManager();
+
+        NsdServiceInfo request1 = new NsdServiceInfo("a_name", "a_type");
+        NsdServiceInfo request2 = new NsdServiceInfo("another_name", "another_type");
+        request1.setPort(2201);
+        request2.setPort(2202);
+        NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+        NsdManager.RegistrationListener listener2 = mock(NsdManager.RegistrationListener.class);
+
+        // Register two services
+        manager.registerService(request1, PROTOCOL, listener1);
+        int key1 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+        manager.registerService(request2, PROTOCOL, listener2);
+        int key2 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+        // First reques fails, second request succeeds
+        sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key2, request2);
+        verify(listener2, timeout(mTimeoutMs).times(1)).onServiceRegistered(request2);
+
+        int err = 1;
+        sendResponse(NsdManager.REGISTER_SERVICE_FAILED, err, key1, request1);
+        verify(listener1, timeout(mTimeoutMs).times(1)).onRegistrationFailed(request1, err);
+
+        // Client retries first request, it succeeds
+        manager.registerService(request1, PROTOCOL, listener1);
+        int key3 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+        sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key3, request1);
+        verify(listener1, timeout(mTimeoutMs).times(1)).onServiceRegistered(request1);
+
+        // First request is unregistered, it succeeds
+        manager.unregisterService(listener1);
+        int key3again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+        assertEquals(key3, key3again);
+
+        sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key3again, null);
+        verify(listener1, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request1);
+
+        // Second request is unregistered, it fails
+        manager.unregisterService(listener2);
+        int key2again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+        assertEquals(key2, key2again);
+
+        sendResponse(NsdManager.UNREGISTER_SERVICE_FAILED, err, key2again, null);
+        verify(listener2, timeout(mTimeoutMs).times(1)).onUnregistrationFailed(request2, err);
+
+        // TODO: do not unregister listener until service is unregistered
+        // Client retries unregistration of second request, it succeeds
+        //manager.unregisterService(listener2);
+        //int key2yetAgain = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+        //assertEquals(key2, key2yetAgain);
+
+        //sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key2yetAgain, null);
+        //verify(listener2, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request2);
+    }
+
+    @Test
+    public void testDiscoverService() {
+        NsdManager manager = makeManager();
+
+        NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type");
+        NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type");
+        NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type");
+
+        NsdManager.DiscoveryListener listener = mock(NsdManager.DiscoveryListener.class);
+
+        // Client registers for discovery, request fails
+        manager.discoverServices("a_type", PROTOCOL, listener);
+        int key1 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+        int err = 1;
+        sendResponse(NsdManager.DISCOVER_SERVICES_FAILED, err, key1, null);
+        verify(listener, timeout(mTimeoutMs).times(1)).onStartDiscoveryFailed("a_type", err);
+
+        // Client retries, request succeeds
+        manager.discoverServices("a_type", PROTOCOL, listener);
+        int key2 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+        sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key2, reply1);
+        verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+
+        // mdns notifies about services
+        sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply1);
+        verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply1);
+
+        sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply2);
+        verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply2);
+
+        sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply2);
+        verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply2);
+
+
+        // Client unregisters its listener
+        manager.stopServiceDiscovery(listener);
+        int key2again = verifyRequest(NsdManager.STOP_DISCOVERY);
+        assertEquals(key2, key2again);
+
+        // TODO: unregister listener immediately and stop notifying it about services
+        // Notifications are still passed to the client's listener
+        sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply1);
+        verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply1);
+
+        // Client is notified of complete unregistration
+        sendResponse(NsdManager.STOP_DISCOVERY_SUCCEEDED, 0, key2again, "a_type");
+        verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStopped("a_type");
+
+        // Notifications are not passed to the client anymore
+        sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply3);
+        verify(listener, timeout(mTimeoutMs).times(0)).onServiceLost(reply3);
+
+
+        // Client registers for service discovery
+        reset(listener);
+        manager.discoverServices("a_type", PROTOCOL, listener);
+        int key3 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+        sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key3, reply1);
+        verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+        // Client unregisters immediately, it fails
+        manager.stopServiceDiscovery(listener);
+        int key3again = verifyRequest(NsdManager.STOP_DISCOVERY);
+        assertEquals(key3, key3again);
+
+        err = 2;
+        sendResponse(NsdManager.STOP_DISCOVERY_FAILED, err, key3again, "a_type");
+        verify(listener, timeout(mTimeoutMs).times(1)).onStopDiscoveryFailed("a_type", err);
+
+        // New notifications are not passed to the client anymore
+        sendResponse(NsdManager.SERVICE_FOUND, 0, key3, reply1);
+        verify(listener, timeout(mTimeoutMs).times(0)).onServiceFound(reply1);
+    }
+
+    @Test
+    public void testInvalidCalls() {
+        NsdManager manager = new NsdManager(mContext, mService);
+
+        NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+        NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
+        NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+
+        NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
+        NsdServiceInfo validService = new NsdServiceInfo("a_name", "a_type");
+        validService.setPort(2222);
+
+        // Service registration
+        //  - invalid arguments
+        mustFail(() -> { manager.unregisterService(null); });
+        mustFail(() -> { manager.registerService(null, -1, null); });
+        mustFail(() -> { manager.registerService(null, PROTOCOL, listener1); });
+        mustFail(() -> { manager.registerService(invalidService, PROTOCOL, listener1); });
+        mustFail(() -> { manager.registerService(validService, -1, listener1); });
+        mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
+        manager.registerService(validService, PROTOCOL, listener1);
+        //  - listener already registered
+        mustFail(() -> { manager.registerService(validService, PROTOCOL, listener1); });
+        manager.unregisterService(listener1);
+        // TODO: make listener immediately reusable
+        //mustFail(() -> { manager.unregisterService(listener1); });
+        //manager.registerService(validService, PROTOCOL, listener1);
+
+        // Discover service
+        //  - invalid arguments
+        mustFail(() -> { manager.stopServiceDiscovery(null); });
+        mustFail(() -> { manager.discoverServices(null, -1, null); });
+        mustFail(() -> { manager.discoverServices(null, PROTOCOL, listener2); });
+        mustFail(() -> { manager.discoverServices("a_service", -1, listener2); });
+        mustFail(() -> { manager.discoverServices("a_service", PROTOCOL, null); });
+        manager.discoverServices("a_service", PROTOCOL, listener2);
+        //  - listener already registered
+        mustFail(() -> { manager.discoverServices("another_service", PROTOCOL, listener2); });
+        manager.stopServiceDiscovery(listener2);
+        // TODO: make listener immediately reusable
+        //mustFail(() -> { manager.stopServiceDiscovery(listener2); });
+        //manager.discoverServices("another_service", PROTOCOL, listener2);
+
+        // Resolver service
+        //  - invalid arguments
+        mustFail(() -> { manager.resolveService(null, null); });
+        mustFail(() -> { manager.resolveService(null, listener3); });
+        mustFail(() -> { manager.resolveService(invalidService, listener3); });
+        mustFail(() -> { manager.resolveService(validService, null); });
+        manager.resolveService(validService, listener3);
+        //  - listener already registered:w
+        mustFail(() -> { manager.resolveService(validService, listener3); });
+    }
+
+    public void mustFail(Runnable fn) {
+        try {
+            fn.run();
+            fail();
+        } catch (Exception expected) {
+        }
+    }
+
+    NsdManager makeManager() {
+        NsdManager manager = new NsdManager(mContext, mService);
+        // Acknowledge first two messages connecting the AsyncChannel.
+        verify(mServiceHandler, timeout(mTimeoutMs).times(2)).handleMessage(any());
+        reset(mServiceHandler);
+        assertNotNull(mServiceHandler.chan);
+        return manager;
+    }
+
+    int verifyRequest(int expectedMessageType) {
+        verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
+        reset(mServiceHandler);
+        Message received = mServiceHandler.lastMessage;
+        assertEquals(NsdManager.nameOf(expectedMessageType), NsdManager.nameOf(received.what));
+        return received.arg2;
+    }
+
+    void sendResponse(int replyType, int arg, int key, Object obj) {
+        mServiceHandler.chan.sendMessage(replyType, arg, key, obj);
+    }
+
+    // Implements the server side of AsyncChannel connection protocol
+    public static class MockServiceHandler extends Handler {
+        public Context mContext;
+        public AsyncChannel chan;
+        public volatile Message lastMessage;
+
+        MockServiceHandler(Looper looper, Context context) {
+            super(looper);
+            mContext = context;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            lastMessage = obtainMessage();
+            lastMessage.copyFrom(msg);
+            if (msg.what == AsyncChannel.CMD_CHANNEL_FULL_CONNECTION) {
+                chan = new AsyncChannel();
+                chan.connect(mContext, this, msg.replyTo);
+                chan.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
+            }
+
+        }
+
+        public static MockServiceHandler create(Context context) {
+            HandlerThread t = new HandlerThread("mock-service-handler");
+            t.start();
+            return new MockServiceHandler(t.getLooper(), context);
+        }
+    }
+}
diff --git a/tests/CoreTests/android/core/NsdServiceInfoTest.java b/tests/net/java/android/net/nsd/NsdServiceInfoTest.java
similarity index 78%
rename from tests/CoreTests/android/core/NsdServiceInfoTest.java
rename to tests/net/java/android/net/nsd/NsdServiceInfoTest.java
index 5bf0167..e48b522 100644
--- a/tests/CoreTests/android/core/NsdServiceInfoTest.java
+++ b/tests/net/java/android/net/nsd/NsdServiceInfoTest.java
@@ -1,11 +1,32 @@
-package android.core;
+/*
+ * 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.
+ */
 
-import android.test.AndroidTestCase;
+package android.net.nsd;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.StrictMode;
 import android.net.nsd.NsdServiceInfo;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
 import java.util.Arrays;
@@ -14,8 +35,12 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class NsdServiceInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdServiceInfoTest {
 
     public final static InetAddress LOCALHOST;
     static {
@@ -30,6 +55,7 @@
         LOCALHOST = _host;
     }
 
+    @Test
     public void testLimits() throws Exception {
         NsdServiceInfo info = new NsdServiceInfo();
 
@@ -85,6 +111,7 @@
         assertTrue(info.getTxtRecord().length == 1300);
     }
 
+    @Test
     public void testParcel() throws Exception {
         NsdServiceInfo emptyInfo = new NsdServiceInfo();
         checkParcelable(emptyInfo);
@@ -139,25 +166,25 @@
         NsdServiceInfo result = reader.getParcelable("test_info");
 
         // Assert equality of base fields.
-        assertEquality(original.getServiceName(), result.getServiceName());
-        assertEquality(original.getServiceType(), result.getServiceType());
-        assertEquality(original.getHost(), result.getHost());
+        assertEquals(original.getServiceName(), result.getServiceName());
+        assertEquals(original.getServiceType(), result.getServiceType());
+        assertEquals(original.getHost(), result.getHost());
         assertTrue(original.getPort() == result.getPort());
 
         // Assert equality of attribute map.
         Map<String, byte[]> originalMap = original.getAttributes();
         Map<String, byte[]> resultMap = result.getAttributes();
-        assertEquality(originalMap.keySet(), resultMap.keySet());
+        assertEquals(originalMap.keySet(), resultMap.keySet());
         for (String key : originalMap.keySet()) {
             assertTrue(Arrays.equals(originalMap.get(key), resultMap.get(key)));
         }
     }
 
-    public void assertEquality(Object expected, Object result) {
-        assertTrue(expected == result || expected.equals(result));
-    }
-
     public void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) {
-        assertTrue(null == shouldBeEmpty.getTxtRecord());
+        byte[] txtRecord = shouldBeEmpty.getTxtRecord();
+        if (txtRecord == null || txtRecord.length == 0) {
+            return;
+        }
+        fail("NsdServiceInfo.getTxtRecord did not return null but " + Arrays.toString(txtRecord));
     }
 }
diff --git a/tests/net/java/com/android/internal/util/BitUtilsTest.java b/tests/net/java/com/android/internal/util/BitUtilsTest.java
new file mode 100644
index 0000000..0ad8a21
--- /dev/null
+++ b/tests/net/java/com/android/internal/util/BitUtilsTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 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.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import java.nio.ByteBuffer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static com.android.internal.util.BitUtils.*;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BitUtilsTest {
+
+    @Test
+    public void testUnsignedByteWideningConversions() {
+        byte b0 = 0;
+        byte b1 = 1;
+        byte bm1 = -1;
+        assertEquals(0, uint8(b0));
+        assertEquals(1, uint8(b1));
+        assertEquals(127, uint8(Byte.MAX_VALUE));
+        assertEquals(128, uint8(Byte.MIN_VALUE));
+        assertEquals(255, uint8(bm1));
+        assertEquals(255, uint8((byte)255));
+    }
+
+    @Test
+    public void testUnsignedShortWideningConversions() {
+        short s0 = 0;
+        short s1 = 1;
+        short sm1 = -1;
+        assertEquals(0, uint16(s0));
+        assertEquals(1, uint16(s1));
+        assertEquals(32767, uint16(Short.MAX_VALUE));
+        assertEquals(32768, uint16(Short.MIN_VALUE));
+        assertEquals(65535, uint16(sm1));
+        assertEquals(65535, uint16((short)65535));
+    }
+
+    @Test
+    public void testUnsignedIntWideningConversions() {
+        assertEquals(0, uint32(0));
+        assertEquals(1, uint32(1));
+        assertEquals(2147483647L, uint32(Integer.MAX_VALUE));
+        assertEquals(2147483648L, uint32(Integer.MIN_VALUE));
+        assertEquals(4294967295L, uint32(-1));
+        assertEquals(4294967295L, uint32((int)4294967295L));
+    }
+
+    @Test
+    public void testBytesToInt() {
+        assertEquals(0x00000000, bytesToBEInt(bytes(0, 0, 0, 0)));
+        assertEquals(0xffffffff, bytesToBEInt(bytes(255, 255, 255, 255)));
+        assertEquals(0x0a000001, bytesToBEInt(bytes(10, 0, 0, 1)));
+        assertEquals(0x0a000002, bytesToBEInt(bytes(10, 0, 0, 2)));
+        assertEquals(0x0a001fff, bytesToBEInt(bytes(10, 0, 31, 255)));
+        assertEquals(0xe0000001, bytesToBEInt(bytes(224, 0, 0, 1)));
+
+        assertEquals(0x00000000, bytesToLEInt(bytes(0, 0, 0, 0)));
+        assertEquals(0x01020304, bytesToLEInt(bytes(4, 3, 2, 1)));
+        assertEquals(0xffff0000, bytesToLEInt(bytes(0, 0, 255, 255)));
+    }
+
+    @Test
+    public void testUnsignedGetters() {
+      ByteBuffer b = ByteBuffer.allocate(4);
+      b.putInt(0xffff);
+
+      assertEquals(0x0, getUint8(b, 0));
+      assertEquals(0x0, getUint8(b, 1));
+      assertEquals(0xff, getUint8(b, 2));
+      assertEquals(0xff, getUint8(b, 3));
+
+      assertEquals(0x0, getUint16(b, 0));
+      assertEquals(0xffff, getUint16(b, 2));
+
+      b.rewind();
+      b.putInt(0xffffffff);
+      assertEquals(0xffffffffL, getUint32(b, 0));
+    }
+
+    static byte[] bytes(int b1, int b2, int b3, int b4) {
+        return new byte[] {b(b1), b(b2), b(b3), b(b4)};
+    }
+
+    static byte b(int i) {
+        return (byte) i;
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6e5ac8a..198ddc6 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -44,6 +44,7 @@
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivityManager.PacketKeepalive;
 import android.net.ConnectivityManager.PacketKeepaliveCallback;
+import android.net.ConnectivityManager.TooManyRequestsException;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
 import android.net.IpPrefix;
@@ -97,6 +98,7 @@
 import com.android.server.net.NetworkPinner;
 import com.android.server.net.NetworkPolicyManagerInternal;
 
+import org.junit.Ignore;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
@@ -115,7 +117,7 @@
  * Tests for {@link ConnectivityService}.
  *
  * Build, install and run with:
- *  runtest frameworks-services -c com.android.server.ConnectivityServiceTest
+ *  runtest frameworks-net -c com.android.server.ConnectivityServiceTest
  */
 public class ConnectivityServiceTest extends AndroidTestCase {
     private static final String TAG = "ConnectivityServiceTest";
@@ -223,13 +225,32 @@
         }
     }
 
+    public void waitForIdle(int timeoutMs) {
+        waitForIdleHandler(mService.mHandlerThread, timeoutMs);
+        waitForIdle(mCellNetworkAgent, timeoutMs);
+        waitForIdle(mWiFiNetworkAgent, timeoutMs);
+        waitForIdle(mEthernetNetworkAgent, timeoutMs);
+        waitForIdleHandler(mService.mHandlerThread, timeoutMs);
+    }
+
+    public void waitForIdle(MockNetworkAgent agent, int timeoutMs) {
+        if (agent == null) {
+            return;
+        }
+        waitForIdleHandler(agent.mHandlerThread, timeoutMs);
+    }
+
+    private void waitForIdle() {
+        waitForIdle(TIMEOUT_MS);
+    }
+
     @SmallTest
     public void testWaitForIdle() {
         final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
 
         // Tests that waitForIdle returns immediately if the service is already idle.
         for (int i = 0; i < attempts; i++) {
-            mService.waitForIdle();
+            waitForIdle();
         }
 
         // Bring up a network that we can use to send messages to ConnectivityService.
@@ -243,7 +264,7 @@
         // Tests that calling waitForIdle waits for messages to be processed.
         for (int i = 0; i < attempts; i++) {
             mWiFiNetworkAgent.setSignalStrength(i);
-            mService.waitForIdle();
+            waitForIdle();
             assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength());
         }
     }
@@ -344,23 +365,19 @@
             };
             // Waits for the NetworkAgent to be registered, which includes the creation of the
             // NetworkMonitor.
-            mService.waitForIdle();
+            waitForIdle();
             mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
         }
 
-        public void waitForIdle(int timeoutMs) {
-            waitForIdleHandler(mHandlerThread, timeoutMs);
-        }
-
-        public void waitForIdle() {
-            waitForIdle(TIMEOUT_MS);
-        }
-
         public void adjustScore(int change) {
             mScore += change;
             mNetworkAgent.sendNetworkScore(mScore);
         }
 
+        public void explicitlySelected(boolean acceptUnvalidated) {
+            mNetworkAgent.explicitlySelected(acceptUnvalidated);
+        }
+
         public void addCapability(int capability) {
             mNetworkCapabilities.addCapability(capability);
             mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
@@ -796,17 +813,27 @@
 
         // Ensure that the default setting for Captive Portals is used for most tests
         setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
+        setMobileDataAlwaysOn(false);
     }
 
     public void tearDown() throws Exception {
         setMobileDataAlwaysOn(false);
-        if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); }
-        if (mWiFiNetworkAgent != null) { mWiFiNetworkAgent.disconnect(); }
-        mCellNetworkAgent = mWiFiNetworkAgent = null;
+        if (mCellNetworkAgent != null) {
+            mCellNetworkAgent.disconnect();
+            mCellNetworkAgent = null;
+        }
+        if (mWiFiNetworkAgent != null) {
+            mWiFiNetworkAgent.disconnect();
+            mWiFiNetworkAgent = null;
+        }
+        if (mEthernetNetworkAgent != null) {
+            mEthernetNetworkAgent.disconnect();
+            mEthernetNetworkAgent = null;
+        }
         super.tearDown();
     }
 
-    private int transportToLegacyType(int transport) {
+    private static int transportToLegacyType(int transport) {
         switch (transport) {
             case TRANSPORT_ETHERNET:
                 return TYPE_ETHERNET;
@@ -838,7 +865,8 @@
         }
         // Test getNetworkInfo(Network)
         assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork()));
-        assertEquals(transportToLegacyType(transport), mCm.getNetworkInfo(mCm.getActiveNetwork()).getType());
+        assertEquals(transportToLegacyType(transport),
+                mCm.getNetworkInfo(mCm.getActiveNetwork()).getType());
         // Test getNetworkCapabilities(Network)
         assertNotNull(mCm.getNetworkCapabilities(mCm.getActiveNetwork()));
         assertTrue(mCm.getNetworkCapabilities(mCm.getActiveNetwork()).hasTransport(transport));
@@ -909,7 +937,7 @@
                 mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
         // Test cellular linger timeout.
         waitFor(mCellNetworkAgent.getDisconnectedCV());
-        mService.waitForIdle();
+        waitForIdle();
         assertEquals(1, mCm.getAllNetworks().length);
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertEquals(1, mCm.getAllNetworks().length);
@@ -932,11 +960,11 @@
         // Test bringing up unvalidated cellular
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
-        mService.waitForIdle();
+        waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test cellular disconnect.
         mCellNetworkAgent.disconnect();
-        mService.waitForIdle();
+        waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up validated cellular
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1288,7 +1316,7 @@
         }
 
         void assertNoCallback() {
-            mService.waitForIdle();
+            waitForIdle();
             CallbackInfo c = mCallbacks.peek();
             assertNull("Unexpected callback: " + c, c);
         }
@@ -1329,7 +1357,7 @@
 
         // This should not trigger spurious onAvailable() callbacks, b/21762680.
         mCellNetworkAgent.adjustScore(-1);
-        mService.waitForIdle();
+        waitForIdle();
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
@@ -1367,7 +1395,7 @@
 
         // This should not trigger spurious onAvailable() callbacks, b/21762680.
         mCellNetworkAgent.adjustScore(-1);
-        mService.waitForIdle();
+        waitForIdle();
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
@@ -1479,7 +1507,7 @@
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
 
         mCm.unregisterNetworkCallback(callback);
-        mService.waitForIdle();
+        waitForIdle();
 
         // Check that a network is only lingered or torn down if it would not satisfy a request even
         // if it validated.
@@ -1594,13 +1622,104 @@
         final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
         callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
 
+        // Register a TRACK_DEFAULT request and check that it does not affect lingering.
+        TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
+        mCm.registerDefaultNetworkCallback(trackDefaultCallback);
+        trackDefaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent.connect(true);
+        callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+        callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
+        trackDefaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+
+        // Let linger run its course.
+        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
+
         // Clean up.
-        mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        mEthernetNetworkAgent.disconnect();
+        callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+        defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+        trackDefaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
 
         mCm.unregisterNetworkCallback(callback);
         mCm.unregisterNetworkCallback(defaultCallback);
+        mCm.unregisterNetworkCallback(trackDefaultCallback);
+    }
+
+    @SmallTest
+    public void testExplicitlySelected() {
+        NetworkRequest request = new NetworkRequest.Builder()
+                .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(request, callback);
+
+        // Bring up validated cell.
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+
+        // Bring up unvalidated wifi with explicitlySelected=true.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(false);
+        mWiFiNetworkAgent.connect(false);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+
+        // Cell Remains the default.
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        // Lower wifi's score to below than cell, and check that it doesn't disconnect because
+        // it's explicitly selected.
+        mWiFiNetworkAgent.adjustScore(-40);
+        mWiFiNetworkAgent.adjustScore(40);
+        callback.assertNoCallback();
+
+        // If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to
+        // wifi even though it's unvalidated.
+        mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false);
+        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        // Disconnect wifi, and then reconnect, again with explicitlySelected=true.
+        mWiFiNetworkAgent.disconnect();
+        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(false);
+        mWiFiNetworkAgent.connect(false);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+
+        // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
+        // network to disconnect.
+        mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false);
+        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // Reconnect, again with explicitlySelected=true, but this time validate.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(false);
+        mWiFiNetworkAgent.connect(true);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        // BUG: the network will no longer linger, even though it's validated and outscored.
+        // TODO: fix this.
+        mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent.connect(true);
+        callback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+        assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        callback.assertNoCallback();
+
+        // Clean up.
+        mWiFiNetworkAgent.disconnect();
+        mCellNetworkAgent.disconnect();
+        mEthernetNetworkAgent.disconnect();
+
+        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
     }
 
     private void tryNetworkFactoryRequests(int capability) throws Exception {
@@ -1749,26 +1868,30 @@
         ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV();
         mCellNetworkAgent.connectWithoutInternet();
         waitFor(cv);
-        mService.waitForIdle();
+        waitForIdle();
         assertEquals(0, mCm.getAllNetworks().length);
         verifyNoNetwork();
+
         // Test bringing up validated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.connect(true);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
+
         // Register MMS NetworkRequest
         NetworkRequest.Builder builder = new NetworkRequest.Builder();
         builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.requestNetwork(builder.build(), networkCallback);
+
         // Test bringing up unvalidated cellular with MMS
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mCellNetworkAgent.connectWithoutInternet();
         networkCallback.expectAvailableCallbacks(mCellNetworkAgent);
         verifyActiveNetwork(TRANSPORT_WIFI);
+
         // Test releasing NetworkRequest disconnects cellular with MMS
         cv = mCellNetworkAgent.getDisconnectedCV();
         mCm.unregisterNetworkCallback(networkCallback);
@@ -1784,17 +1907,20 @@
         mCellNetworkAgent.connect(false);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
+
         // Register MMS NetworkRequest
         NetworkRequest.Builder builder = new NetworkRequest.Builder();
         builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.requestNetwork(builder.build(), networkCallback);
+
         // Test bringing up MMS cellular network
         MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mmsNetworkAgent.connectWithoutInternet();
         networkCallback.expectAvailableCallbacks(mmsNetworkAgent);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
+
         // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
         cv = mmsNetworkAgent.getDisconnectedCV();
         mCm.unregisterNetworkCallback(networkCallback);
@@ -2067,6 +2193,40 @@
     }
 
     @SmallTest
+    public void testNetworkSpecifierUidSpoofSecurityException() {
+        class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+            @Override
+            public boolean satisfiedBy(NetworkSpecifier other) {
+                return true;
+            }
+
+            @Override
+            public void assertValidFromUid(int requestorUid) {
+                throw new SecurityException("failure");
+            }
+
+            @Override
+            public int describeContents() { return 0; }
+            @Override
+            public void writeToParcel(Parcel dest, int flags) {}
+        }
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+
+        UidAwareNetworkSpecifier networkSpecifier = new UidAwareNetworkSpecifier();
+        NetworkRequest networkRequest = newWifiRequestBuilder().setNetworkSpecifier(
+                networkSpecifier).build();
+        TestNetworkCallback networkCallback = new TestNetworkCallback();
+        try {
+            mCm.requestNetwork(networkRequest, networkCallback);
+            fail("Network request with spoofed UID did not throw a SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @SmallTest
     public void testRegisterDefaultNetworkCallback() throws Exception {
         final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
@@ -2167,7 +2327,7 @@
         ContentResolver cr = mServiceContext.getContentResolver();
         Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0);
         mService.updateMobileDataAlwaysOn();
-        mService.waitForIdle();
+        waitForIdle();
     }
 
     private boolean isForegroundNetwork(MockNetworkAgent network) {
@@ -2209,7 +2369,7 @@
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
         // When lingering is complete, cell is still there but is now in the background.
-        mService.waitForIdle();
+        waitForIdle();
         int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
         fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
         // Expect a network capabilities update sans FOREGROUND.
@@ -2254,8 +2414,8 @@
         mCm.unregisterNetworkCallback(fgCallback);
     }
 
-    @SmallTest
-    public void testRequestBenchmark() throws Exception {
+    @Ignore // This test has instrinsic chances of spurious failures: ignore for continuous testing.
+    public void benchmarkRequestRegistrationAndCallbackDispatch() 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.
@@ -2265,6 +2425,11 @@
         //    and NUM_REQUESTS onAvailable callbacks to fire.
         // See how long it took.
         final int NUM_REQUESTS = 90;
+        final int REGISTER_TIME_LIMIT_MS = 200;
+        final int CONNECT_TIME_LIMIT_MS = 60;
+        final int SWITCH_TIME_LIMIT_MS = 60;
+        final int UNREGISTER_TIME_LIMIT_MS = 20;
+
         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
         final NetworkCallback[] callbacks = new NetworkCallback[NUM_REQUESTS];
         final CountDownLatch availableLatch = new CountDownLatch(NUM_REQUESTS);
@@ -2277,14 +2442,12 @@
             };
         }
 
-        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,
@@ -2292,31 +2455,29 @@
         mCellNetworkAgent.connect(false);
 
         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));
-            }
+            awaitLatch(availableLatch, 10 * CONNECT_TIME_LIMIT_MS);
         });
-        Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS));
+        Log.d(TAG, String.format("Dispatched %d of %d onAvailable callbacks in %dms",
+                NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
+                onAvailableDispatchingDuration));
+        assertTrue(String.format("Dispatching %d onAvailable callbacks in %dms, expected %dms",
+                NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS),
+                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 = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.adjustScore(40);
         mWiFiNetworkAgent.connect(false);
 
         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));
-            }
+            awaitLatch(losingLatch, 10 * SWITCH_TIME_LIMIT_MS);
         });
-        Log.d(TAG, String.format("Linger, %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS));
+        Log.d(TAG, String.format("Dispatched %d of %d onLosing callbacks in %dms",
+                NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, onLostDispatchingDuration));
+        assertTrue(String.format("Dispatching %d onLosing callbacks in %dms, expected %dms",
+                NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS),
+                onLostDispatchingDuration <= SWITCH_TIME_LIMIT_MS);
 
-        final int UNREGISTER_TIME_LIMIT_MS = 10;
         assertTimeLimit("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
             for (NetworkCallback cb : callbacks) {
                 mCm.unregisterNetworkCallback(cb);
@@ -2339,9 +2500,7 @@
 
     private boolean awaitLatch(CountDownLatch l, long timeoutMs) {
         try {
-            if (l.await(timeoutMs, TimeUnit.MILLISECONDS)) {
-                return true;
-            }
+            return l.await(timeoutMs, TimeUnit.MILLISECONDS);
         } catch (InterruptedException e) {}
         return false;
     }
@@ -2393,7 +2552,7 @@
         assertFalse(testFactory.getMyStartRequested());  // Because the cell network outscores us.
 
         // Check that cell data stays up.
-        mService.waitForIdle();
+        waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertEquals(2, mCm.getAllNetworks().length);
 
@@ -2422,7 +2581,7 @@
         for (int i = 0; i < values.length; i++) {
             Settings.Global.putInt(cr, settingName, 1);
             tracker.reevaluate();
-            mService.waitForIdle();
+            waitForIdle();
             String msg = String.format("config=false, setting=%s", values[i]);
             assertTrue(mService.avoidBadWifi());
             assertFalse(msg, tracker.shouldNotifyWifiUnvalidated());
@@ -2432,19 +2591,19 @@
 
         Settings.Global.putInt(cr, settingName, 0);
         tracker.reevaluate();
-        mService.waitForIdle();
+        waitForIdle();
         assertFalse(mService.avoidBadWifi());
         assertFalse(tracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putInt(cr, settingName, 1);
         tracker.reevaluate();
-        mService.waitForIdle();
+        waitForIdle();
         assertTrue(mService.avoidBadWifi());
         assertFalse(tracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putString(cr, settingName, null);
         tracker.reevaluate();
-        mService.waitForIdle();
+        waitForIdle();
         assertFalse(mService.avoidBadWifi());
         assertTrue(tracker.shouldNotifyWifiUnvalidated());
     }
@@ -2587,7 +2746,7 @@
                 tracker.configMeteredMultipathPreference = config;
                 Settings.Global.putString(cr, settingName, setting);
                 tracker.reevaluate();
-                mService.waitForIdle();
+                waitForIdle();
 
                 final int expected = (setting != null) ? Integer.parseInt(setting) : config;
                 String msg = String.format("config=%d, setting=%s", config, setting);
@@ -2775,7 +2934,7 @@
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
         mWiFiNetworkAgent.sendLinkProperties(lp);
-        mService.waitForIdle();
+        waitForIdle();
         return mWiFiNetworkAgent.getNetwork();
     }
 
@@ -2854,7 +3013,7 @@
         callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
 
         // ... and that stopping it after that has no adverse effects.
-        mService.waitForIdle();
+        waitForIdle();
         final Network myNetAlias = myNet;
         assertNull(mCm.getNetworkCapabilities(myNetAlias));
         ka.stop();
@@ -2869,7 +3028,7 @@
         ka.stop();
         mWiFiNetworkAgent.disconnect();
         waitFor(mWiFiNetworkAgent.getDisconnectedCV());
-        mService.waitForIdle();
+        waitForIdle();
         callback.expectStopped();
 
         // Reconnect.
@@ -3021,7 +3180,7 @@
                 networkCallbacks.add(networkCallback);
             }
             fail("Registering " + MAX_REQUESTS + " NetworkRequests did not throw exception");
-        } catch (IllegalArgumentException expected) {}
+        } catch (TooManyRequestsException expected) {}
         for (NetworkCallback networkCallback : networkCallbacks) {
             mCm.unregisterNetworkCallback(networkCallback);
         }
@@ -3034,7 +3193,7 @@
                 networkCallbacks.add(networkCallback);
             }
             fail("Registering " + MAX_REQUESTS + " NetworkCallbacks did not throw exception");
-        } catch (IllegalArgumentException expected) {}
+        } catch (TooManyRequestsException expected) {}
         for (NetworkCallback networkCallback : networkCallbacks) {
             mCm.unregisterNetworkCallback(networkCallback);
         }
@@ -3050,7 +3209,7 @@
             }
             fail("Registering " + MAX_REQUESTS +
                     " PendingIntent NetworkRequests did not throw exception");
-        } catch (IllegalArgumentException expected) {}
+        } catch (TooManyRequestsException expected) {}
         for (PendingIntent pendingIntent : pendingIntents) {
             mCm.unregisterNetworkCallback(pendingIntent);
         }
@@ -3065,12 +3224,12 @@
             }
             fail("Registering " + MAX_REQUESTS +
                     " PendingIntent NetworkCallbacks did not throw exception");
-        } catch (IllegalArgumentException expected) {}
+        } catch (TooManyRequestsException expected) {}
         for (PendingIntent pendingIntent : pendingIntents) {
             mCm.unregisterNetworkCallback(pendingIntent);
         }
         pendingIntents.clear();
-        mService.waitForIdle(5000);
+        waitForIdle(5000);
 
         // Test that the limit is not hit when MAX_REQUESTS requests are added and removed.
         for (int i = 0; i < MAX_REQUESTS; i++) {
@@ -3078,20 +3237,20 @@
             mCm.requestNetwork(networkRequest, networkCallback);
             mCm.unregisterNetworkCallback(networkCallback);
         }
-        mService.waitForIdle();
+        waitForIdle();
         for (int i = 0; i < MAX_REQUESTS; i++) {
             NetworkCallback networkCallback = new NetworkCallback();
             mCm.registerNetworkCallback(networkRequest, networkCallback);
             mCm.unregisterNetworkCallback(networkCallback);
         }
-        mService.waitForIdle();
+        waitForIdle();
         for (int i = 0; i < MAX_REQUESTS; i++) {
             PendingIntent pendingIntent =
                     PendingIntent.getBroadcast(mContext, 0, new Intent("b" + i), 0);
             mCm.requestNetwork(networkRequest, pendingIntent);
             mCm.unregisterNetworkCallback(pendingIntent);
         }
-        mService.waitForIdle();
+        waitForIdle();
         for (int i = 0; i < MAX_REQUESTS; i++) {
             PendingIntent pendingIntent =
                     PendingIntent.getBroadcast(mContext, 0, new Intent("c" + i), 0);
diff --git a/tests/net/java/com/android/server/NsdServiceTest.java b/tests/net/java/com/android/server/NsdServiceTest.java
new file mode 100644
index 0000000..68cb251
--- /dev/null
+++ b/tests/net/java/com/android/server/NsdServiceTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import com.android.server.NsdService.DaemonConnection;
+import com.android.server.NsdService.DaemonConnectionSupplier;
+import com.android.server.NsdService.NativeCallbackReceiver;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+// TODOs:
+//  - test client can send requests and receive replies
+//  - test NSD_ON ENABLE/DISABLED listening
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdServiceTest {
+
+    static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+
+    long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+
+    @Mock Context mContext;
+    @Mock ContentResolver mResolver;
+    @Mock NsdService.NsdSettings mSettings;
+    @Mock DaemonConnection mDaemon;
+    NativeCallbackReceiver mDaemonCallback;
+    HandlerThread mThread;
+    TestHandler mHandler;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mThread = new HandlerThread("mock-service-handler");
+        mThread.start();
+        mHandler = new TestHandler(mThread.getLooper());
+        when(mContext.getContentResolver()).thenReturn(mResolver);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mThread.quit();
+    }
+
+    @Test
+    public void testClientsCanConnectAndDisconnect() {
+        when(mSettings.isEnabled()).thenReturn(true);
+
+        NsdService service = makeService();
+
+        NsdManager client1 = connectClient(service);
+        verify(mDaemon, timeout(100).times(1)).start();
+
+        NsdManager client2 = connectClient(service);
+
+        client1.disconnect();
+        client2.disconnect();
+
+        verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
+    }
+
+    @Test
+    public void testClientRequestsAreGCedAtDisconnection() {
+        when(mSettings.isEnabled()).thenReturn(true);
+        when(mDaemon.execute(any())).thenReturn(true);
+
+        NsdService service = makeService();
+        NsdManager client = connectClient(service);
+
+        verify(mDaemon, timeout(100).times(1)).start();
+
+        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+        request.setPort(2201);
+
+        // Client registration request
+        NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+        client.registerService(request, PROTOCOL, listener1);
+        verifyDaemonCommand("register 2 a_name a_type 2201");
+
+        // Client discovery request
+        NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
+        client.discoverServices("a_type", PROTOCOL, listener2);
+        verifyDaemonCommand("discover 3 a_type");
+
+        // Client resolve request
+        NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+        client.resolveService(request, listener3);
+        verifyDaemonCommand("resolve 4 a_name a_type local.");
+
+        // Client disconnects
+        client.disconnect();
+        verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
+
+        // checks that request are cleaned
+        verifyDaemonCommands("stop-register 2", "stop-discover 3", "stop-resolve 4");
+    }
+
+    NsdService makeService() {
+        DaemonConnectionSupplier supplier = (callback) -> {
+            mDaemonCallback = callback;
+            return mDaemon;
+        };
+        NsdService service = new NsdService(mContext, mSettings, mHandler, supplier);
+        verify(mDaemon, never()).execute(any(String.class));
+        return service;
+    }
+
+    NsdManager connectClient(NsdService service) {
+        return new NsdManager(mContext, service);
+    }
+
+    void verifyDaemonCommands(String... wants) {
+        verifyDaemonCommand(String.join(" ", wants), wants.length);
+    }
+
+    void verifyDaemonCommand(String want) {
+        verifyDaemonCommand(want, 1);
+    }
+
+    void verifyDaemonCommand(String want, int n) {
+        ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
+        verify(mDaemon, timeout(mTimeoutMs).times(n)).execute(argumentsCaptor.capture());
+        String got = "";
+        for (Object o : argumentsCaptor.getAllValues()) {
+            got += o + " ";
+        }
+        assertEquals(want, got.trim());
+        // rearm deamon for next command verification
+        reset(mDaemon);
+        when(mDaemon.execute(any())).thenReturn(true);
+    }
+
+    public static class TestHandler extends Handler {
+        public Message lastMessage;
+
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            lastMessage = obtainMessage();
+            lastMessage.copyFrom(msg);
+        }
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index f201bc7..911347c 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -16,6 +16,16 @@
 
 package com.android.server.connectivity;
 
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
@@ -37,15 +47,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 public class NetworkNotificationManagerTest extends TestCase {
 
     static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
@@ -140,4 +141,47 @@
 
         verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
     }
+
+    @SmallTest
+    public void testDuplicatedNotificationsNoInternetThenSignIn() {
+        final int id = 101;
+        final String tag = NetworkNotificationManager.tagFor(id);
+
+        // Show first NO_INTERNET
+        mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(NO_INTERNET.eventId), any(), any());
+
+        // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET
+        mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .cancelAsUser(eq(tag), eq(NO_INTERNET.eventId), any());
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(SIGN_IN.eventId), any(), any());
+
+        // Network disconnects
+        mManager.clearNotification(id);
+        verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
+    }
+
+    @SmallTest
+    public void testDuplicatedNotificationsSignInThenNoInternet() {
+        final int id = 101;
+        final String tag = NetworkNotificationManager.tagFor(id);
+
+        // Show first SIGN_IN
+        mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(SIGN_IN.eventId), any(), any());
+        reset(mNotificationManager);
+
+        // NO_INTERNET arrives after, but is ignored.
+        mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, never()).cancelAsUser(any(), anyInt(), any());
+        verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
+
+        // Network disconnects
+        mManager.clearNotification(id);
+        verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index b51b277..506d9e5 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -27,10 +27,13 @@
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.UidRange;
+import android.os.Build;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -40,11 +43,14 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import com.android.internal.net.VpnConfig;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Map;
 import java.util.Set;
 
+import org.mockito.Answers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
@@ -87,23 +93,32 @@
         }
     }
 
-    @Mock private Context mContext;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext;
     @Mock private UserManager mUserManager;
     @Mock private PackageManager mPackageManager;
     @Mock private INetworkManagementService mNetService;
     @Mock private AppOpsManager mAppOps;
     @Mock private NotificationManager mNotificationManager;
+    @Mock private Vpn.SystemServices mSystemServices;
 
     @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);
+
+        // Used by {@link Notification.Builder}
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+        when(mContext.getApplicationInfo()).thenReturn(applicationInfo);
+
         doNothing().when(mNetService).registerObserver(any());
     }
 
@@ -111,7 +126,7 @@
     public void testRestrictedProfilesAreAddedToVpn() {
         setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
 
-        final Vpn vpn = spyVpn(primaryUser.id);
+        final Vpn vpn = createVpn(primaryUser.id);
         final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
                 null, null);
 
@@ -125,7 +140,7 @@
     public void testManagedProfilesAreNotAddedToVpn() {
         setMockedUsers(primaryUser, managedProfileA);
 
-        final Vpn vpn = spyVpn(primaryUser.id);
+        final Vpn vpn = createVpn(primaryUser.id);
         final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
                 null, null);
 
@@ -138,7 +153,7 @@
     public void testAddUserToVpnOnlyAddsOneUser() {
         setMockedUsers(primaryUser, restrictedProfileA, managedProfileA);
 
-        final Vpn vpn = spyVpn(primaryUser.id);
+        final Vpn vpn = createVpn(primaryUser.id);
         final Set<UidRange> ranges = new ArraySet<>();
         vpn.addUserToRanges(ranges, primaryUser.id, null, null);
 
@@ -149,7 +164,7 @@
 
     @SmallTest
     public void testUidWhiteAndBlacklist() throws Exception {
-        final Vpn vpn = spyVpn(primaryUser.id);
+        final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
         final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
 
@@ -174,7 +189,7 @@
 
     @SmallTest
     public void testLockdownChangingPackage() throws Exception {
-        final Vpn vpn = spyVpn(primaryUser.id);
+        final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
 
         // Default state.
@@ -209,7 +224,7 @@
 
     @SmallTest
     public void testLockdownAddingAProfile() throws Exception {
-        final Vpn vpn = spyVpn(primaryUser.id);
+        final Vpn vpn = createVpn(primaryUser.id);
         setMockedUsers(primaryUser);
 
         // Make a copy of the restricted profile, as we're going to mark it deleted halfway through.
@@ -248,41 +263,94 @@
     }
 
     @SmallTest
+    public void testLockdownRuleRepeatability() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+
+        // Given legacy lockdown is already enabled,
+        vpn.setLockdown(true);
+        verify(mNetService, times(1)).setAllowOnlyVpnForUids(
+                eq(true), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));
+
+        // Enabling legacy lockdown twice should do nothing.
+        vpn.setLockdown(true);
+        verify(mNetService, times(1)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
+
+        // And disabling should remove the rules exactly once.
+        vpn.setLockdown(false);
+        verify(mNetService, times(1)).setAllowOnlyVpnForUids(
+                eq(false), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));
+
+        // Removing the lockdown again should have no effect.
+        vpn.setLockdown(false);
+        verify(mNetService, times(2)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
+    }
+
+    @SmallTest
+    public void testLockdownRuleReversibility() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+
+        final UidRange[] entireUser = {
+            UidRange.createForUser(primaryUser.id)
+        };
+        final UidRange[] exceptPkg0 = {
+            new UidRange(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
+            new UidRange(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
+        };
+
+        final InOrder order = inOrder(mNetService);
+
+        // Given lockdown is enabled with no package (legacy VPN),
+        vpn.setLockdown(true);
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
+
+        // When a new VPN package is set the rules should change to cover that package.
+        vpn.prepare(null, PKGS[0]);
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser));
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0));
+
+        // When that VPN package is unset, everything should be undone again in reverse.
+        vpn.prepare(null, VpnConfig.LEGACY_VPN);
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0));
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
+    }
+
+    @SmallTest
     public void testNotificationShownForAlwaysOnApp() {
-        final Vpn vpn = spyVpn(primaryUser.id);
-        final InOrder order = inOrder(vpn);
+        final UserHandle userHandle = UserHandle.of(primaryUser.id);
+        final Vpn vpn = createVpn(primaryUser.id);
         setMockedUsers(primaryUser);
 
+        final InOrder order = inOrder(mNotificationManager);
+
         // Don't show a notification for regular disconnected states.
         vpn.updateState(DetailedState.DISCONNECTED, TAG);
-        order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+        order.verify(mNotificationManager, atLeastOnce())
+                .cancelAsUser(anyString(), anyInt(), eq(userHandle));
 
         // Start showing a notification for disconnected once always-on.
         vpn.setAlwaysOnPackage(PKGS[0], false);
-        order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+        order.verify(mNotificationManager)
+                .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
         // Stop showing the notification once connected.
         vpn.updateState(DetailedState.CONNECTED, TAG);
-        order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+        order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
 
         // Show the notification if we disconnect again.
         vpn.updateState(DetailedState.DISCONNECTED, TAG);
-        order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+        order.verify(mNotificationManager)
+                .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
         // Notification should be cleared after unsetting always-on package.
         vpn.setAlwaysOnPackage(null, false);
-        order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+        order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
     }
 
     /**
      * Mock some methods of vpn object.
      */
-    private Vpn spyVpn(@UserIdInt int userId) {
-        final Vpn vpn = spy(new Vpn(Looper.myLooper(), mContext, mNetService, userId));
-
-        // Block calls to the NotificationManager or PendingIntent#getActivity.
-        doNothing().when(vpn).updateAlwaysOnNotificationInternal(anyBoolean());
-        return vpn;
+    private Vpn createVpn(@UserIdInt int userId) {
+        return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices);
     }
 
     private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
similarity index 91%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
index bb8f9d1..23318c2 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.net;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.when;
 
 import android.Manifest;
@@ -25,16 +26,22 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 
 import com.android.server.LocalServices;
 
-import junit.framework.TestCase;
-
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-public class NetworkStatsAccessTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsAccessTest {
     private static final String TEST_PKG = "com.example.test";
     private static final int TEST_UID = 12345;
 
@@ -46,9 +53,8 @@
     // Hold the real service so we can restore it when tearing down the test.
     private DevicePolicyManagerInternal mSystemDpmi;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
         MockitoAnnotations.initMocks(this);
 
         mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
@@ -59,13 +65,13 @@
         when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps);
     }
 
-    @Override
+    @After
     public void tearDown() throws Exception {
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
         LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi);
-        super.tearDown();
     }
 
+    @Test
     public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
         setHasCarrierPrivileges(true);
         setIsDeviceOwner(false);
@@ -76,6 +82,7 @@
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_isDeviceOwner() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(true);
@@ -86,6 +93,7 @@
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_isProfileOwner() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
@@ -96,36 +104,40 @@
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
         setIsProfileOwner(true);
         setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
         setHasReadHistoryPermission(false);
-        assertEquals(NetworkStatsAccess.Level.USER,
+        assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
         setIsProfileOwner(true);
         setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
         setHasReadHistoryPermission(false);
-        assertEquals(NetworkStatsAccess.Level.USER,
+        assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
         setIsProfileOwner(true);
         setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
         setHasReadHistoryPermission(true);
-        assertEquals(NetworkStatsAccess.Level.USER,
+        assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
@@ -136,6 +148,7 @@
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 9f53c87..2a32b73 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -31,11 +31,11 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
+import android.support.test.filters.SmallTest;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.MediumTest;
 
-import com.android.frameworks.servicestests.R;
+import com.android.frameworks.tests.net.R;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -51,7 +51,7 @@
 /**
  * Tests for {@link NetworkStatsCollection}.
  */
-@MediumTest
+@SmallTest
 public class NetworkStatsCollectionTest extends AndroidTestCase {
 
     private static final String TEST_FILE = "test.bin";
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
similarity index 93%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index 5eee7b9..92dcdac 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -18,36 +18,39 @@
 
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.isA;
-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.SET_DEFAULT;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
+import static org.junit.Assert.assertEquals;
+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.isA;
+import static org.mockito.Mockito.when;
+
 import android.app.usage.NetworkStatsManager;
 import android.net.DataUsageRequest;
 import android.net.NetworkIdentity;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
+import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Process;
-
-import android.os.ConditionVariable;
 import android.os.Looper;
-import android.os.Messenger;
 import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
 import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 
@@ -60,8 +63,9 @@
 import java.util.Objects;
 import java.util.List;
 
-import junit.framework.TestCase;
-
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -69,7 +73,9 @@
 /**
  * Tests for {@link NetworkStatsObservers}.
  */
-public class NetworkStatsObserversTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsObserversTest {
     private static final String TEST_IFACE = "test0";
     private static final String TEST_IFACE2 = "test1";
     private static final long TEST_START = 1194220800000L;
@@ -87,7 +93,7 @@
     private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
     private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
 
-    private static final long WAIT_TIMEOUT = 500;  // 1/2 sec
+    private static final long WAIT_TIMEOUT_MS = 500;
     private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
     private static final long BASE_BYTES = 7 * MB_IN_BYTES;
     private static final int INVALID_TYPE = -1;
@@ -100,7 +106,6 @@
     private Handler mObserverNoopHandler;
 
     private LatchedHandler mHandler;
-    private ConditionVariable mCv;
 
     private NetworkStatsObservers mStatsObservers;
     private Messenger mMessenger;
@@ -109,9 +114,8 @@
 
     @Mock private IBinder mockBinder;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
         MockitoAnnotations.initMocks(this);
 
         mObserverHandlerThread = new IdleableHandlerThread("HandlerThread");
@@ -124,14 +128,14 @@
             }
         };
 
-        mCv = new ConditionVariable();
-        mHandler = new LatchedHandler(Looper.getMainLooper(), mCv);
+        mHandler = new LatchedHandler(Looper.getMainLooper(), new ConditionVariable());
         mMessenger = new Messenger(mHandler);
 
         mActiveIfaces = new ArrayMap<>();
         mActiveUidIfaces = new ArrayMap<>();
     }
 
+    @Test
     public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
         long thresholdTooLowBytes = 1L;
         DataUsageRequest inputRequest = new DataUsageRequest(
@@ -144,6 +148,7 @@
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
     }
 
+    @Test
     public void testRegister_highThreshold_accepted() throws Exception {
         long highThresholdBytes = 2 * THRESHOLD_BYTES;
         DataUsageRequest inputRequest = new DataUsageRequest(
@@ -156,6 +161,7 @@
         assertEquals(highThresholdBytes, request.thresholdInBytes);
     }
 
+    @Test
     public void testRegister_twoRequests_twoIds() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
@@ -173,6 +179,7 @@
         assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
     }
 
+    @Test
     public void testUnregister_unknownRequest_noop() throws Exception {
         DataUsageRequest unknownRequest = new DataUsageRequest(
                 123456 /* id */, sTemplateWifi, THRESHOLD_BYTES);
@@ -180,6 +187,7 @@
         mStatsObservers.unregister(unknownRequest, UID_RED);
     }
 
+    @Test
     public void testUnregister_knownRequest_releasesCaller() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -197,6 +205,7 @@
         Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
     }
 
+    @Test
     public void testUnregister_knownRequest_invalidUid_doesNotUnregister() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -214,6 +223,7 @@
         Mockito.verifyZeroInteractions(mockBinder);
     }
 
+    @Test
     public void testUpdateStats_initialSample_doesNotNotify() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -239,11 +249,9 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
-        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
     }
 
+    @Test
     public void testUpdateStats_belowThreshold_doesNotNotify() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -275,12 +283,10 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
-        mCv.block(WAIT_TIMEOUT);
-        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
     }
 
+
+    @Test
     public void testUpdateStats_deviceAccess_notifies() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -313,11 +319,10 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
         assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
     }
 
+    @Test
     public void testUpdateStats_defaultAccess_notifiesSameUid() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -351,11 +356,10 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
         assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
     }
 
+    @Test
     public void testUpdateStats_defaultAccess_usageOtherUid_doesNotNotify() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -389,11 +393,9 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
-        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
     }
 
+    @Test
     public void testUpdateStats_userAccess_usageSameUser_notifies() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -427,11 +429,10 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
         assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
     }
 
+    @Test
     public void testUpdateStats_userAccess_usageAnotherUser_doesNotNotify() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -466,14 +467,22 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
-        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
     }
 
     private void waitForObserverToIdle() {
-        // Send dummy message to make sure that any previous message has been handled
-        mHandler.sendMessage(mHandler.obtainMessage(-1));
-        mObserverHandlerThread.waitForIdle(WAIT_TIMEOUT);
+        waitForIdleLooper(mObserverHandlerThread.getLooper(), WAIT_TIMEOUT_MS);
+        waitForIdleLooper(mHandler.getLooper(), WAIT_TIMEOUT_MS);
     }
+
+    // TODO: unify with ConnectivityService.waitForIdleHandler and
+    // NetworkServiceStatsTest.IdleableHandlerThread
+    private static void waitForIdleLooper(Looper looper, long timeoutMs) {
+        final ConditionVariable cv = new ConditionVariable();
+        final Handler handler = new Handler(looper);
+        handler.post(() -> cv.open());
+        if (!cv.block(timeoutMs)) {
+            fail("Looper did not become idle after " + timeoutMs + " ms");
+        }
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 728eb73..029693f 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -85,6 +85,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
@@ -119,6 +120,7 @@
  * still uses the Easymock structure, which could be simplified.
  */
 @RunWith(AndroidJUnit4.class)
+@SmallTest
 public class NetworkStatsServiceTest {
     private static final String TAG = "NetworkStatsServiceTest";
 
diff --git a/services/tests/servicestests/res/raw/netstats_uid_v4 b/tests/net/res/raw/netstats_uid_v4
similarity index 100%
rename from services/tests/servicestests/res/raw/netstats_uid_v4
rename to tests/net/res/raw/netstats_uid_v4
Binary files differ
diff --git a/services/tests/servicestests/res/raw/netstats_v1 b/tests/net/res/raw/netstats_v1
similarity index 100%
rename from services/tests/servicestests/res/raw/netstats_v1
rename to tests/net/res/raw/netstats_v1
Binary files differ