Merge changes from topic 'framework-net-aosp'

* changes:
  DO NOT MERGE: [CS] Remove timeout event after first available
  DO NOT MERGE: ApfTest: tag tests with @SmallTest or @MediumTest
  DO NOT MERGE: Unbreak TetherInterfaceStateMachineTest.
  DO NOT MERGE: Move the connectivity tests to frameworks/base/tests/net.
  DO NOT MERGE: De-guava BroadcastInterceptingContext and move it to testutils.
  DO NOT MERGE: Move FakeSettingsProvider to a common location.
  DO NOT MERGE: ConnectivityServiceTest: mark flaky test as such
  DO NOT MERGE: Add option to skip and avoid captive portals.
  DO NOT MERGE: ApfFilter: use elapsedRealTime for RA lifetime
  DO NOT MERGE: Do not synchronize boolean reads/writes
  DO NOT MERGE: ApfFilter: systematically use u8, u16, u32 getters
  DO NOT MERGE: Add fuzzing tests to ApfFilter RA processing
  DO NOT MERGE: Support timeouts for requestNetwork() invocations.
  DO NOT MERGE: Silence the obnoxious MTU 0 error message that occur when no MTU is specified for a given network.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 0afb546..43c8c81 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2592,7 +2592,8 @@
 
         /**
          * Called if no network is found in the given timeout time.  If no timeout is given,
-         * this will not be called.
+         * this will not be called. The associated {@link NetworkRequest} will have already
+         * been removed and released, as if {@link #unregisterNetworkCallback} had been called.
          * @hide
          */
         public void onUnavailable() {}
@@ -2665,6 +2666,26 @@
     /** @hide */
     public static final int CALLBACK_RESUMED             = BASE + 12;
 
+    /** @hide */
+    public static String getCallbackName(int whichCallback) {
+        switch (whichCallback) {
+            case CALLBACK_PRECHECK:     return "CALLBACK_PRECHECK";
+            case CALLBACK_AVAILABLE:    return "CALLBACK_AVAILABLE";
+            case CALLBACK_LOSING:       return "CALLBACK_LOSING";
+            case CALLBACK_LOST:         return "CALLBACK_LOST";
+            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";
+            default:
+                return Integer.toString(whichCallback);
+        }
+    }
+
     private class CallbackHandler extends Handler {
         private final HashMap<NetworkRequest, NetworkCallback>mCallbackMap;
         private final AtomicInteger mRefCount;
@@ -2831,7 +2852,7 @@
     private final static int REQUEST = 2;
 
     private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
-            NetworkCallback networkCallback, int timeoutSec, int action,
+            NetworkCallback networkCallback, int timeoutMs, int action,
             int legacyType) {
         if (networkCallback == null) {
             throw new IllegalArgumentException("null NetworkCallback");
@@ -2847,7 +2868,7 @@
                             new Messenger(sCallbackHandler), new Binder());
                 } else {
                     networkCallback.networkRequest = mService.requestNetwork(need,
-                            new Messenger(sCallbackHandler), timeoutSec, new Binder(), legacyType);
+                            new Messenger(sCallbackHandler), timeoutMs, new Binder(), legacyType);
                 }
                 if (networkCallback.networkRequest != null) {
                     sNetworkCallback.put(networkCallback.networkRequest, networkCallback);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1350bfa..db7baac 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8028,11 +8028,45 @@
         public static final String PAC_CHANGE_DELAY = "pac_change_delay";
 
         /**
-         * Setting to turn off captive portal detection. Feature is enabled by
-         * default and the setting needs to be set to 0 to disable it.
+         * Don't attempt to detect captive portals.
          *
          * @hide
          */
+        public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0;
+
+        /**
+         * When detecting a captive portal, display a notification that
+         * prompts the user to sign in.
+         *
+         * @hide
+         */
+        public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1;
+
+        /**
+         * When detecting a captive portal, immediately disconnect from the
+         * network and do not reconnect to that network in the future.
+         *
+         * @hide
+         */
+        public static final int CAPTIVE_PORTAL_MODE_AVOID = 2;
+
+        /**
+         * What to do when connecting a network that presents a captive portal.
+         * Must be one of the CAPTIVE_PORTAL_MODE_* constants above.
+         *
+         * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
+         * @hide
+         */
+        public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
+
+        /**
+         * Setting to turn off captive portal detection. Feature is enabled by
+         * default and the setting needs to be set to 0 to disable it.
+         *
+         * @deprecated use CAPTIVE_PORTAL_MODE_IGNORE to disable captive portal detection
+         * @hide
+         */
+        @Deprecated
         public static final String
                 CAPTIVE_PORTAL_DETECTION_ENABLED = "captive_portal_detection_enabled";
 
diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk
index 6d1ebb4..981be11 100644
--- a/core/tests/utiltests/Android.mk
+++ b/core/tests/utiltests/Android.mk
@@ -14,6 +14,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
+    frameworks-base-testutils \
     mockito-target
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java b/core/tests/utiltests/src/com/android/internal/util/test/FakeSettingsProviderTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java
rename to core/tests/utiltests/src/com/android/internal/util/test/FakeSettingsProviderTest.java
index 05de0a5..f2be109 100644
--- a/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/test/FakeSettingsProviderTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.util;
+package com.android.internal.util.test;
 
 import android.content.ContentResolver;
 import android.database.ContentObserver;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 30edd9f..090c744 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1813,11 +1813,14 @@
     private void updateMtu(LinkProperties newLp, LinkProperties oldLp) {
         final String iface = newLp.getInterfaceName();
         final int mtu = newLp.getMtu();
+        if (oldLp == null && mtu == 0) {
+            // Silently ignore unset MTU value.
+            return;
+        }
         if (oldLp != null && newLp.isIdenticalMtu(oldLp)) {
             if (VDBG) log("identical MTU - not setting");
             return;
         }
-
         if (LinkProperties.isValidMtu(mtu, newLp.hasGlobalIPv6Address()) == false) {
             if (mtu != 0) loge("Unexpected mtu value: " + mtu + ", " + iface);
             return;
@@ -2263,11 +2266,19 @@
                     synchronized (mNetworkForNetId) {
                         nai = mNetworkForNetId.get(netId);
                     }
-                    // If captive portal status has changed, update capabilities.
+                    // If captive portal status has changed, update capabilities or disconnect.
                     if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
                         final int oldScore = nai.getCurrentScore();
                         nai.lastCaptivePortalDetected = visible;
                         nai.everCaptivePortalDetected |= visible;
+                        if (nai.lastCaptivePortalDetected &&
+                            Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
+                            if (DBG) log("Avoiding captive portal network: " + nai.name());
+                            nai.asyncChannel.sendMessage(
+                                    NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
+                            teardownUnneededNetwork(nai);
+                            break;
+                        }
                         updateCapabilities(oldScore, nai, nai.networkCapabilities);
                     }
                     if (!visible) {
@@ -2288,6 +2299,12 @@
             return true;
         }
 
+        private int getCaptivePortalMode() {
+            return Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.CAPTIVE_PORTAL_MODE,
+                    Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
+        }
+
         private boolean maybeHandleNetworkAgentInfoMessage(Message msg) {
             switch (msg.what) {
                 default:
@@ -2597,14 +2614,28 @@
                 "request NetworkCapabilities", ConnectivityManager.CALLBACK_CAP_CHANGED);
     }
 
+    private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
+        if (mNetworkRequests.get(nri.request) != null && mNetworkForRequestId.get(
+                nri.request.requestId) == null) {
+            handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_UNAVAIL);
+        }
+    }
+
     private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
         final NetworkRequestInfo nri = getNriForAppRequest(
                 request, callingUid, "release NetworkRequest");
-        if (nri == null) return;
+        if (nri != null) {
+            handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_RELEASED);
+        }
+    }
 
-        if (VDBG || (DBG && nri.request.isRequest())) log("releasing " + request);
+    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 + ")");
+        }
         nri.unlinkDeathRecipient();
-        mNetworkRequests.remove(request);
+        mNetworkRequests.remove(nri.request);
         synchronized (mUidToNetworkRequestCount) {
             int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
             if (requests < 1) {
@@ -2698,7 +2729,7 @@
                 }
             }
         }
-        callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED, 0);
+        callCallbackForRequest(nri, null, whichCallback, 0);
     }
 
     @Override
@@ -2935,6 +2966,11 @@
                     handleRegisterNetworkRequestWithIntent(msg);
                     break;
                 }
+                case EVENT_TIMEOUT_NETWORK_REQUEST: {
+                    NetworkRequestInfo nri = (NetworkRequestInfo) msg.obj;
+                    handleTimedOutNetworkRequest(nri);
+                    break;
+                }
                 case EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT: {
                     handleReleaseNetworkRequestWithIntent((PendingIntent) msg.obj, msg.arg1);
                     break;
@@ -5315,6 +5351,7 @@
     // notify only this one new request of the current state
     protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
         int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
+        mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
         if (nri.mPendingIntent == null) {
             callCallbackForRequest(nri, nai, notifyType, 0);
         } else {
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 6eb89fa..c73d1dd 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -211,7 +211,9 @@
     private final NetworkRequest mDefaultRequest;
     private final IpConnectivityLog mMetricsLog;
 
-    private boolean mIsCaptivePortalCheckEnabled;
+    @VisibleForTesting
+    protected boolean mIsCaptivePortalCheckEnabled;
+
     private boolean mUseHttps;
 
     // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
@@ -265,7 +267,8 @@
         setInitialState(mDefaultState);
 
         mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
+                Settings.Global.CAPTIVE_PORTAL_MODE, Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT)
+                != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
         mUseHttps = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
 
@@ -632,7 +635,10 @@
 
     @VisibleForTesting
     protected CaptivePortalProbeResult isCaptivePortal() {
-        if (!mIsCaptivePortalCheckEnabled) return new CaptivePortalProbeResult(204);
+        if (!mIsCaptivePortalCheckEnabled) {
+            validationLog("Validation disabled.");
+            return new CaptivePortalProbeResult(204);
+        }
 
         URL pacUrl = null, httpsUrl = null, httpUrl = null, fallbackUrl = null;
 
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 0beb227..6d96a10 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -71,6 +71,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.server.connectivity.tethering.IControlsTethering;
 import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
+import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
 import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
 import com.android.server.net.BaseNetworkObserver;
 
@@ -1939,7 +1940,8 @@
     private void trackNewTetherableInterface(String iface, int interfaceType) {
         TetherState tetherState;
         tetherState = new TetherState(new TetherInterfaceStateMachine(iface, mLooper,
-                interfaceType, mNMService, mStatsService, this));
+                interfaceType, mNMService, mStatsService, this,
+                new IPv6TetheringInterfaceServices(iface, mNMService)));
         mTetherStates.put(iface, tetherState);
         tetherState.mStateMachine.start();
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
index c2c1a8c..dec2f77 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
@@ -45,7 +45,7 @@
 /**
  * @hide
  */
-class IPv6TetheringInterfaceServices {
+public class IPv6TetheringInterfaceServices {
     private static final String TAG = IPv6TetheringInterfaceServices.class.getSimpleName();
     private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64");
     private static final int RFC7421_IP_PREFIX_LENGTH = 64;
@@ -59,7 +59,7 @@
     private RouterAdvertisementDaemon mRaDaemon;
     private RaParams mLastRaParams;
 
-    IPv6TetheringInterfaceServices(String ifname, INetworkManagementService nms) {
+    public IPv6TetheringInterfaceServices(String ifname, INetworkManagementService nms) {
         mIfName = ifname;
         mNMService = nms;
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 6ca4e27..37221a9 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -94,14 +94,14 @@
 
     public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType,
                     INetworkManagementService nMService, INetworkStatsService statsService,
-                    IControlsTethering tetherController) {
+                    IControlsTethering tetherController, IPv6TetheringInterfaceServices ipv6Svc) {
         super(ifaceName, looper);
         mNMService = nMService;
         mStatsService = statsService;
         mTetherController = tetherController;
         mIfaceName = ifaceName;
         mInterfaceType = interfaceType;
-        mIPv6TetherSvc = new IPv6TetheringInterfaceServices(mIfaceName, mNMService);
+        mIPv6TetherSvc = ipv6Svc;
         mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
 
         mInitialState = new InitialState();
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 4c75452..a8356dc 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -283,14 +283,20 @@
         mReceiveThread.start();
     }
 
-    // Returns seconds since Unix Epoch.
-    // TODO: use SystemClock.elapsedRealtime() instead
+    // Returns seconds since device boot.
     private static long curTime() {
-        return System.currentTimeMillis() / DateUtils.SECOND_IN_MILLIS;
+        return SystemClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS;
+    }
+
+    public static class InvalidRaException extends Exception {
+        public InvalidRaException(String m) {
+            super(m);
+        }
     }
 
     // A class to hold information about an RA.
-    private class Ra {
+    @VisibleForTesting
+    class Ra {
         // From RFC4861:
         private static final int ICMP6_RA_HEADER_LEN = 16;
         private static final int ICMP6_RA_CHECKSUM_OFFSET =
@@ -362,7 +368,7 @@
             } catch (UnsupportedOperationException e) {
                 // array() failed. Cannot happen, mPacket is array-backed and read-write.
                 return "???";
-            } catch (ClassCastException | UnknownHostException e) {
+            } catch (ClassCastException|UnknownHostException e) {
                 // Cannot happen.
                 return "???";
             }
@@ -372,16 +378,16 @@
         // TODO: Make this static once RA is its own class.
         private void prefixOptionToString(StringBuffer sb, int offset) {
             String prefix = IPv6AddresstoString(offset + 16);
-            int length = uint8(mPacket.get(offset + 2));
-            long valid = mPacket.getInt(offset + 4);
-            long preferred = mPacket.getInt(offset + 8);
+            int length = getUint8(mPacket, offset + 2);
+            long valid = getUint32(mPacket, offset + 4);
+            long preferred = getUint32(mPacket, offset + 8);
             sb.append(String.format("%s/%d %ds/%ds ", prefix, length, valid, preferred));
         }
 
         private void rdnssOptionToString(StringBuffer sb, int offset) {
-            int optLen = uint8(mPacket.get(offset + 1)) * 8;
+            int optLen = getUint8(mPacket, offset + 1) * 8;
             if (optLen < 24) return;  // Malformed or empty.
-            long lifetime = uint32(mPacket.getInt(offset + 4));
+            long lifetime = getUint32(mPacket, offset + 4);
             int numServers = (optLen - 8) / 16;
             sb.append("DNS ").append(lifetime).append("s");
             for (int server = 0; server < numServers; server++) {
@@ -395,7 +401,7 @@
                 sb.append(String.format("RA %s -> %s %ds ",
                         IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET),
                         IPv6AddresstoString(IPV6_DEST_ADDR_OFFSET),
-                        uint16(mPacket.getShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET))));
+                        getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET)));
                 for (int i: mPrefixOptionOffsets) {
                     prefixOptionToString(sb, i);
                 }
@@ -403,7 +409,7 @@
                     rdnssOptionToString(sb, i);
                 }
                 return sb.toString();
-            } catch (BufferUnderflowException | IndexOutOfBoundsException e) {
+            } catch (BufferUnderflowException|IndexOutOfBoundsException e) {
                 return "<Malformed RA>";
             }
         }
@@ -436,16 +442,20 @@
         // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException
         // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with
         // specifications.
-        Ra(byte[] packet, int length) {
+        Ra(byte[] packet, int length) throws InvalidRaException {
+            if (length < ICMP6_RA_OPTION_OFFSET) {
+                throw new InvalidRaException("Not an ICMP6 router advertisement");
+            }
+
             mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length));
             mLastSeen = curTime();
 
             // Sanity check packet in case a packet arrives before we attach RA filter
             // to our packet socket. b/29586253
             if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 ||
-                    uint8(mPacket.get(IPV6_NEXT_HEADER_OFFSET)) != IPPROTO_ICMPV6 ||
-                    uint8(mPacket.get(ICMP6_TYPE_OFFSET)) != ICMP6_ROUTER_ADVERTISEMENT) {
-                throw new IllegalArgumentException("Not an ICMP6 router advertisement");
+                    getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 ||
+                    getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMP6_ROUTER_ADVERTISEMENT) {
+                throw new InvalidRaException("Not an ICMP6 router advertisement");
             }
 
 
@@ -466,8 +476,8 @@
             mPacket.position(ICMP6_RA_OPTION_OFFSET);
             while (mPacket.hasRemaining()) {
                 final int position = mPacket.position();
-                final int optionType = uint8(mPacket.get(position));
-                final int optionLength = uint8(mPacket.get(position + 1)) * 8;
+                final int optionType = getUint8(mPacket, position);
+                final int optionLength = getUint8(mPacket, position + 1) * 8;
                 long lifetime;
                 switch (optionType) {
                     case ICMP6_PREFIX_OPTION_TYPE:
@@ -511,7 +521,7 @@
                         break;
                 }
                 if (optionLength <= 0) {
-                    throw new IllegalArgumentException(String.format(
+                    throw new InvalidRaException(String.format(
                         "Invalid option length opt=%d len=%d", optionType, optionLength));
                 }
                 mPacket.position(position + optionLength);
@@ -552,10 +562,10 @@
                 final long optionLifetime;
                 switch (lifetimeLength) {
                     case 2:
-                        optionLifetime = uint16(byteBuffer.getShort(offset));
+                        optionLifetime = getUint16(byteBuffer, offset);
                         break;
                     case 4:
-                        optionLifetime = uint32(byteBuffer.getInt(offset));
+                        optionLifetime = getUint32(byteBuffer, offset);
                         break;
                     default:
                         throw new IllegalStateException("bogus lifetime size " + lifetimeLength);
@@ -925,8 +935,8 @@
             // Execution will reach the end of the program if no filters match, which will pass the
             // packet to the AP.
             program = gen.generate();
-        } catch (IllegalInstructionException e) {
-            Log.e(TAG, "Program failed to generate: ", e);
+        } catch (IllegalInstructionException|IllegalStateException e) {
+            Log.e(TAG, "Failed to generate APF program.", e);
             return;
         }
         mLastTimeInstalledProgram = curTime();
@@ -972,7 +982,8 @@
      * if the current APF program should be updated.
      * @return a ProcessRaResult enum describing what action was performed.
      */
-    private synchronized ProcessRaResult processRa(byte[] packet, int length) {
+    @VisibleForTesting
+    synchronized ProcessRaResult processRa(byte[] packet, int length) {
         if (VDBG) hexDump("Read packet = ", packet, length);
 
         // Have we seen this RA before?
@@ -1011,7 +1022,7 @@
         try {
             ra = new Ra(packet, length);
         } catch (Exception e) {
-            Log.e(TAG, "Error parsing RA: " + e);
+            Log.e(TAG, "Error parsing RA", e);
             return ProcessRaResult.PARSE_ERROR;
         }
         // Ignore 0 lifetime RAs.
@@ -1150,7 +1161,11 @@
         return i & 0xffffffffL;
     }
 
-    private static long getUint16(ByteBuffer buffer, int position) {
+    private static int getUint8(ByteBuffer buffer, int position) {
+        return uint8(buffer.get(position));
+    }
+
+    private static int getUint16(ByteBuffer buffer, int position) {
         return uint16(buffer.getShort(position));
     }
 
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index a6bb40c..a883e28 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -50,9 +50,9 @@
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -163,8 +163,7 @@
     private Map<InetAddress, Short> mIpWatchList = new HashMap<>();
     @GuardedBy("mLock")
     private int mIpWatchListVersion;
-    @GuardedBy("mLock")
-    private boolean mRunning;
+    private volatile boolean mRunning;
     // Time in milliseconds of the last forced probe request.
     private volatile long mLastProbeTimeMs;
 
@@ -246,7 +245,7 @@
     }
 
     public void stop() {
-        synchronized (mLock) { mRunning = false; }
+        mRunning = false;
         clearLinkProperties();
         mNetlinkSocketObserver.clearNetlinkSocket();
     }
@@ -281,12 +280,6 @@
         }
     }
 
-    private boolean stillRunning() {
-        synchronized (mLock) {
-            return mRunning;
-        }
-    }
-
     private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
         for (RouteInfo route : routes) {
             if (!route.hasGateway() && route.matches(ip)) {
@@ -390,12 +383,12 @@
     }
 
     public void probeAll() {
-        Set<InetAddress> ipProbeList = new HashSet<InetAddress>();
+        final List<InetAddress> ipProbeList;
         synchronized (mLock) {
-            ipProbeList.addAll(mIpWatchList.keySet());
+            ipProbeList = new ArrayList<>(mIpWatchList.keySet());
         }
 
-        if (!ipProbeList.isEmpty() && stillRunning()) {
+        if (!ipProbeList.isEmpty() && mRunning) {
             // Keep the CPU awake long enough to allow all ARP/ND
             // probes a reasonable chance at success. See b/23197666.
             //
@@ -406,7 +399,7 @@
         }
 
         for (InetAddress target : ipProbeList) {
-            if (!stillRunning()) {
+            if (!mRunning) {
                 break;
             }
             final int returnValue = probeNeighbor(mInterfaceIndex, target);
@@ -451,21 +444,21 @@
         @Override
         public void run() {
             if (VDBG) { Log.d(TAG, "Starting observing thread."); }
-            synchronized (mLock) { mRunning = true; }
+            mRunning = true;
 
             try {
                 setupNetlinkSocket();
             } catch (ErrnoException | SocketException e) {
                 Log.e(TAG, "Failed to suitably initialize a netlink socket", e);
-                synchronized (mLock) { mRunning = false; }
+                mRunning = false;
             }
 
-            ByteBuffer byteBuffer;
-            while (stillRunning()) {
+            while (mRunning) {
+                final ByteBuffer byteBuffer;
                 try {
                     byteBuffer = recvKernelReply();
                 } catch (ErrnoException e) {
-                    if (stillRunning()) { Log.w(TAG, "ErrnoException: ", e); }
+                    if (mRunning) { Log.w(TAG, "ErrnoException: ", e); }
                     break;
                 }
                 final long whenMs = SystemClock.elapsedRealtime();
@@ -477,7 +470,7 @@
 
             clearNetlinkSocket();
 
-            synchronized (mLock) { mRunning = false; }
+            mRunning = false; // Not a no-op when ErrnoException happened.
             if (VDBG) { Log.d(TAG, "Finishing observing thread."); }
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
index 0d5daa5..f841bf9 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -24,6 +24,7 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import com.android.server.net.BaseNetworkObserver;
+import com.android.internal.util.test.BroadcastInterceptingContext;
 
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 541be3d..8d36ac9 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -78,6 +78,7 @@
 import android.text.format.Time;
 import android.util.TrustedTime;
 
+import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.google.common.util.concurrent.AbstractFuture;
 
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
index 94c6711..4b69eb3 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
@@ -88,7 +88,7 @@
 import android.util.TrustedTime;
 
 import com.android.internal.net.VpnInfo;
-import com.android.server.BroadcastInterceptingContext;
+import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
new file mode 100644
index 0000000..8aa27a9
--- /dev/null
+++ b/tests/net/Android.mk
@@ -0,0 +1,80 @@
+#########################################################################
+# Build FrameworksNetTests package
+#########################################################################
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    frameworks-base-testutils \
+    framework-protos \
+    android-support-test \
+    mockito-target-minus-junit4 \
+    platform-test-annotations \
+    services.core \
+    services.net
+
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner
+
+LOCAL_PACKAGE_NAME := FrameworksNetTests
+
+LOCAL_CERTIFICATE := platform
+
+# These are not normally accessible from apps so they must be explicitly included.
+LOCAL_JNI_SHARED_LIBRARIES := libframeworksnettestsjni \
+    libbacktrace \
+    libbase \
+    libbinder \
+    libc++ \
+    libcutils \
+    liblog \
+    liblzma \
+    libnativehelper \
+    libnetdaidl \
+    libui \
+    libunwind \
+    libutils
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+include $(BUILD_PACKAGE)
+
+#########################################################################
+# Build JNI Shared Library
+#########################################################################
+
+LOCAL_PATH:= $(LOCAL_PATH)/jni
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+
+LOCAL_C_INCLUDES := \
+  libpcap \
+  hardware/google/apf
+
+LOCAL_SRC_FILES := $(call all-cpp-files-under)
+
+LOCAL_SHARED_LIBRARIES := \
+  libbinder \
+  liblog \
+  libcutils \
+  libnativehelper \
+  libnetdaidl
+
+LOCAL_STATIC_LIBRARIES := \
+  libpcap \
+  libapf
+
+LOCAL_MODULE := libframeworksnettestsjni
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/net/AndroidManifest.xml b/tests/net/AndroidManifest.xml
new file mode 100644
index 0000000..e069dd0
--- /dev/null
+++ b/tests/net/AndroidManifest.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.tests.net">
+
+    <uses-permission android:name="android.permission.READ_LOGS" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+    <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+    <uses-permission android:name="android.permission.GET_DETAILED_TASKS" />
+    <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
+    <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+    <uses-permission android:name="android.permission.MODIFY_NETWORK_ACCOUNTING" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
+    <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.tests.net"
+        android:label="Frameworks Networking Tests" />
+</manifest>
diff --git a/services/tests/servicestests/src/android/net/ConnectivityMetricsLoggerTest.java b/tests/net/java/android/net/ConnectivityMetricsLoggerTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/ConnectivityMetricsLoggerTest.java
rename to tests/net/java/android/net/ConnectivityMetricsLoggerTest.java
diff --git a/services/tests/servicestests/src/android/net/UidRangeTest.java b/tests/net/java/android/net/UidRangeTest.java
similarity index 98%
rename from services/tests/servicestests/src/android/net/UidRangeTest.java
rename to tests/net/java/android/net/UidRangeTest.java
index 221fe0f..0a56e1b 100644
--- a/services/tests/servicestests/src/android/net/UidRangeTest.java
+++ b/tests/net/java/android/net/UidRangeTest.java
@@ -26,7 +26,7 @@
 public class UidRangeTest extends TestCase {
 
     static {
-        System.loadLibrary("servicestestsjni");
+        System.loadLibrary("frameworksnettestsjni");
     }
 
     private static native byte[] readAndWriteNative(byte[] inParcel);
diff --git a/services/tests/servicestests/src/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
similarity index 96%
rename from services/tests/servicestests/src/android/net/apf/ApfTest.java
rename to tests/net/java/android/net/apf/ApfTest.java
index f7c61d1..81f66a5 100644
--- a/services/tests/servicestests/src/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -16,10 +16,6 @@
 
 package android.net.apf;
 
-import static android.system.OsConstants.*;
-
-import com.android.frameworks.servicestests.R;
-
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkUtils;
@@ -36,7 +32,12 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import static android.system.OsConstants.*;
+
+import com.android.frameworks.tests.net.R;
+import com.android.internal.util.HexDump;
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -54,6 +55,7 @@
 import java.net.NetworkInterface;
 import java.nio.ByteBuffer;
 import java.util.List;
+import java.util.Random;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -74,7 +76,7 @@
         super.setUp();
         MockitoAnnotations.initMocks(this);
         // Load up native shared library containing APF interpreter exposed via JNI.
-        System.loadLibrary("servicestestsjni");
+        System.loadLibrary("frameworksnettestsjni");
     }
 
     // Expected return codes from APF interpreter.
@@ -153,7 +155,7 @@
      * generating bytecode for that program and running it through the
      * interpreter to verify it functions correctly.
      */
-    @LargeTest
+    @MediumTest
     public void testApfInstructions() throws IllegalInstructionException {
         // Empty program should pass because having the program counter reach the
         // location immediately after the program indicates the packet should be
@@ -561,7 +563,7 @@
      * Generate some BPF programs, translate them to APF, then run APF and BPF programs
      * over packet traces and verify both programs filter out the same packets.
      */
-    @LargeTest
+    @MediumTest
     public void testApfAgainstBpf() throws Exception {
         String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
                 "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
@@ -719,7 +721,7 @@
     private static final byte[] ANOTHER_IPV4_ADDR        = {10, 0, 0, 2};
     private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
 
-    @LargeTest
+    @MediumTest
     public void testApfFilterIPv4() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
@@ -774,7 +776,7 @@
         apfFilter.shutdown();
     }
 
-    @LargeTest
+    @MediumTest
     public void testApfFilterIPv6() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
@@ -800,7 +802,7 @@
         apfFilter.shutdown();
     }
 
-    @LargeTest
+    @MediumTest
     public void testApfFilterMulticast() throws Exception {
         final byte[] unicastIpv4Addr   = {(byte)192,0,2,63};
         final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
@@ -910,7 +912,7 @@
         assertDrop(program, garpReply());
     }
 
-    @LargeTest
+    @MediumTest
     public void testApfFilterArp() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
@@ -1029,7 +1031,7 @@
         ipManagerCallback.assertNoProgramUpdate();
     }
 
-    @LargeTest
+    @MediumTest
     public void testApfFilterRa() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog);
@@ -1146,6 +1148,41 @@
         buffer.position(original);
     }
 
+    @SmallTest
+    public void testRaParsing() throws Exception {
+        final int maxRandomPacketSize = 512;
+        final Random r = new Random();
+        MockIpManagerCallback cb = new MockIpManagerCallback();
+        TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST, mLog);
+        for (int i = 0; i < 1000; i++) {
+            byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+            r.nextBytes(packet);
+            try {
+                apfFilter.new Ra(packet, packet.length);
+            } catch (ApfFilter.InvalidRaException e) {
+            } catch (Exception e) {
+                throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
+            }
+        }
+    }
+
+    @SmallTest
+    public void testRaProcessing() throws Exception {
+        final int maxRandomPacketSize = 512;
+        final Random r = new Random();
+        MockIpManagerCallback cb = new MockIpManagerCallback();
+        TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST, mLog);
+        for (int i = 0; i < 1000; i++) {
+            byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+            r.nextBytes(packet);
+            try {
+                apfFilter.processRa(packet, packet.length);
+            } catch (Exception e) {
+                throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
+            }
+        }
+    }
+
     /**
      * Call the APF interpreter the run {@code program} on {@code packet} pretending the
      * filter was installed {@code filter_age} seconds ago.
@@ -1167,6 +1204,7 @@
     private native static boolean compareBpfApf(String filter, String pcap_filename,
             byte[] apf_program);
 
+    @SmallTest
     public void testBytesToInt() {
         assertEquals(0x00000000, ApfFilter.bytesToInt(IPV4_ANY_HOST_ADDR));
         assertEquals(0xffffffff, ApfFilter.bytesToInt(IPV4_BROADCAST_ADDRESS));
diff --git a/services/tests/servicestests/src/android/net/apf/Bpf2Apf.java b/tests/net/java/android/net/apf/Bpf2Apf.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/apf/Bpf2Apf.java
rename to tests/net/java/android/net/apf/Bpf2Apf.java
diff --git a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
rename to tests/net/java/android/net/dhcp/DhcpPacketTest.java
diff --git a/services/tests/servicestests/src/android/net/netlink/NetlinkErrorMessageTest.java b/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/netlink/NetlinkErrorMessageTest.java
rename to tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java
diff --git a/services/tests/servicestests/src/android/net/netlink/NetlinkSocketTest.java b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/netlink/NetlinkSocketTest.java
rename to tests/net/java/android/net/netlink/NetlinkSocketTest.java
diff --git a/services/tests/servicestests/src/android/net/netlink/RtNetlinkNeighborMessageTest.java b/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
rename to tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java
diff --git a/services/tests/servicestests/src/android/net/util/IpUtilsTest.java b/tests/net/java/android/net/util/IpUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/util/IpUtilsTest.java
rename to tests/net/java/android/net/util/IpUtilsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
similarity index 93%
rename from services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
rename to tests/net/java/com/android/server/ConnectivityServiceTest.java
index 4af1cf1..fc8cc81 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -68,14 +68,16 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
+import android.test.FlakyTest;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 import android.util.LogPrinter;
 
-import com.android.internal.util.FakeSettingsProvider;
 import com.android.internal.util.WakeupMessage;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult;
@@ -215,8 +217,20 @@
             mService.waitForIdle();
             assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength());
         }
+    }
+
+    @FlakyTest(tolerance = 3)
+    public void testNotWaitingForIdleCausesRaceConditions() {
+        // Bring up a network that we can use to send messages to ConnectivityService.
+        ConditionVariable cv = waitForConnectivityBroadcasts(1);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        waitFor(cv);
+        Network n = mWiFiNetworkAgent.getNetwork();
+        assertNotNull(n);
 
         // Ensure that not calling waitForIdle causes a race condition.
+        final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
         for (int i = 0; i < attempts; i++) {
             mWiFiNetworkAgent.setSignalStrength(i);
             if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) {
@@ -236,6 +250,7 @@
         private final IdleableHandlerThread mHandlerThread;
         private final ConditionVariable mDisconnected = new ConditionVariable();
         private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
+        private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
         private int mScore;
         private NetworkAgent mNetworkAgent;
         private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
@@ -291,6 +306,11 @@
                     mRedirectUrl = redirectUrl;
                     mNetworkStatusReceived.open();
                 }
+
+                @Override
+                protected void preventAutomaticReconnect() {
+                    mPreventReconnectReceived.open();
+                }
             };
             // Waits for the NetworkAgent to be registered, which includes the creation of the
             // NetworkMonitor.
@@ -375,11 +395,6 @@
             mWrappedNetworkMonitor.gen204ProbeResult = 200;
             mWrappedNetworkMonitor.gen204ProbeRedirectUrl = redirectUrl;
             connect(false);
-            waitFor(new Criteria() { public boolean get() {
-                NetworkCapabilities caps = mCm.getNetworkCapabilities(getNetwork());
-                return caps != null && caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL);} });
-            mWrappedNetworkMonitor.gen204ProbeResult = 500;
-            mWrappedNetworkMonitor.gen204ProbeRedirectUrl = null;
         }
 
         public void disconnect() {
@@ -391,6 +406,10 @@
             return new Network(mNetworkAgent.netId);
         }
 
+        public ConditionVariable getPreventReconnectReceived() {
+            return mPreventReconnectReceived;
+        }
+
         public ConditionVariable getDisconnectedCV() {
             return mDisconnected;
         }
@@ -597,6 +616,7 @@
 
         @Override
         protected CaptivePortalProbeResult isCaptivePortal() {
+            if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); }
             return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null);
         }
     }
@@ -707,10 +727,7 @@
     static private void waitFor(Criteria criteria) {
         int delays = 0;
         while (!criteria.get()) {
-            try {
-                Thread.sleep(50);
-            } catch (InterruptedException e) {
-            }
+            sleepFor(50);
             if (++delays == 10) fail();
         }
     }
@@ -743,6 +760,9 @@
         mService.systemReady();
         mCm = new WrappedConnectivityManager(getContext(), mService);
         mCm.bindProcessToNetwork(null);
+
+        // Ensure that the default setting for Captive Portals is used for most tests
+        setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
     }
 
     public void tearDown() throws Exception {
@@ -1077,7 +1097,8 @@
         NETWORK_CAPABILITIES,
         LINK_PROPERTIES,
         LOSING,
-        LOST
+        LOST,
+        UNAVAILABLE
     }
 
     /**
@@ -1118,6 +1139,11 @@
         }
 
         @Override
+        public void onUnavailable() {
+            setLastCallback(CallbackState.UNAVAILABLE, null, null);
+        }
+
+        @Override
         public void onLosing(Network network, int maxMsToLive) {
             setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */);
         }
@@ -1704,6 +1730,47 @@
         validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
     }
 
+    @LargeTest
+    public void testAvoidOrIgnoreCaptivePortals() {
+        final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+        final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+        mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+        final TestNetworkCallback validatedCallback = new TestNetworkCallback();
+        final NetworkRequest validatedRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_VALIDATED).build();
+        mCm.registerNetworkCallback(validatedRequest, validatedCallback);
+
+        setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_AVOID);
+        // Bring up a network with a captive portal.
+        // Expect it to fail to connect and not result in any callbacks.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        String firstRedirectUrl = "http://example.com/firstPath";
+
+        ConditionVariable disconnectCv = mWiFiNetworkAgent.getDisconnectedCV();
+        ConditionVariable avoidCv = mWiFiNetworkAgent.getPreventReconnectReceived();
+        mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
+        waitFor(disconnectCv);
+        waitFor(avoidCv);
+
+        assertNoCallbacks(captivePortalCallback, validatedCallback);
+
+        // Now test ignore mode.
+        setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
+
+        // Bring up a network with a captive portal.
+        // Since we're ignoring captive portals, the network will validate.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        String secondRedirectUrl = "http://example.com/secondPath";
+        mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
+
+        // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
+        validatedCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        // But there should be no CaptivePortal callback.
+        captivePortalCallback.assertNoCallback();
+    }
+
     @SmallTest
     public void testInvalidNetworkSpecifier() {
         boolean execptionCalled = true;
@@ -1844,6 +1911,11 @@
         mCm.unregisterNetworkCallback(cellNetworkCallback);
     }
 
+    private void setCaptivePortalMode(int mode) {
+        ContentResolver cr = mServiceContext.getContentResolver();
+        Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);
+    }
+
     private void setMobileDataAlwaysOn(boolean enable) {
         ContentResolver cr = mServiceContext.getContentResolver();
         Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0);
@@ -2221,6 +2293,96 @@
         mCm.unregisterNetworkCallback(defaultCallback);
     }
 
+    /**
+     * Validate that a satisfied network request does not trigger onUnavailable() once the
+     * time-out period expires.
+     */
+    @SmallTest
+    public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(nr, networkCallback, 10);
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+
+        // pass timeout and validate that UNAVAILABLE is not called
+        sleepFor(15);
+        networkCallback.assertNoCallback();
+    }
+
+    /**
+     * Validate that a satisfied network request followed by a disconnected (lost) network does
+     * not trigger onUnavailable() once the time-out period expires.
+     */
+    @SmallTest
+    public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(nr, networkCallback, 500);
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        sleepFor(20);
+        mWiFiNetworkAgent.disconnect();
+        networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // pass timeout and validate that UNAVAILABLE is not called
+        sleepFor(600);
+        networkCallback.assertNoCallback();
+    }
+
+    /**
+     * Validate that when a time-out is specified for a network request the onUnavailable()
+     * callback is called when time-out expires. Then validate that if network request is
+     * (somehow) satisfied - the callback isn't called later.
+     */
+    @SmallTest
+    public void testTimedoutNetworkRequest() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(nr, networkCallback, 10);
+
+        // pass timeout and validate that UNAVAILABLE is called
+        networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
+
+        // create a network satisfying request - validate that request not triggered
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        networkCallback.assertNoCallback();
+    }
+
+    /**
+     * Validate that when a network request is unregistered (cancelled) the time-out for that
+     * request doesn't trigger the onUnavailable() callback.
+     */
+    @SmallTest
+    public void testTimedoutAfterUnregisteredNetworkRequest() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(nr, networkCallback, 10);
+
+        // remove request
+        mCm.unregisterNetworkCallback(networkCallback);
+
+        // pass timeout and validate that no callbacks
+        // Note: doesn't validate that nothing called from CS since even if called the CM already
+        // unregisters the callback and won't pass it through!
+        sleepFor(15);
+        networkCallback.assertNoCallback();
+
+        // create a network satisfying request - validate that request not triggered
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        networkCallback.assertNoCallback();
+    }
+
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
         public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
@@ -2625,4 +2787,13 @@
             mCm.unregisterNetworkCallback(pendingIntent);
         }
     }
+
+    /* test utilities */
+    static private void sleepFor(int ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+        }
+
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
rename to tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java
rename to tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/connectivity/LingerMonitorTest.java
rename to tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/MetricsLoggerServiceTest.java b/tests/net/java/com/android/server/connectivity/MetricsLoggerServiceTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/connectivity/MetricsLoggerServiceTest.java
rename to tests/net/java/com/android/server/connectivity/MetricsLoggerServiceTest.java
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/MetricsTestUtil.java b/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/connectivity/MetricsTestUtil.java
rename to tests/net/java/com/android/server/connectivity/MetricsTestUtil.java
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java
rename to tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/connectivity/TetheringTest.java
rename to tests/net/java/com/android/server/connectivity/TetheringTest.java
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java
rename to tests/net/java/com/android/server/connectivity/VpnTest.java
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
rename to tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
index a30b362..9f7261d 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -59,13 +59,14 @@
     @Mock private INetworkStatsService mStatsService;
     @Mock private IControlsTethering mTetherHelper;
     @Mock private InterfaceConfiguration mInterfaceConfiguration;
+    @Mock private IPv6TetheringInterfaceServices mIPv6TetheringInterfaceServices;
 
     private final TestLooper mLooper = new TestLooper();
     private TetherInterfaceStateMachine mTestedSm;
 
     private void initStateMachine(int interfaceType) throws Exception {
         mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType,
-                mNMService, mStatsService, mTetherHelper);
+                mNMService, mStatsService, mTetherHelper, mIPv6TetheringInterfaceServices);
         mTestedSm.start();
         // Starting the state machine always puts us in a consistent state and notifies
         // the test of the world that we've changed from an unknown to available state.
@@ -91,7 +92,8 @@
     @Test
     public void startsOutAvailable() {
         mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
-                ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper);
+                ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper,
+                mIPv6TetheringInterfaceServices);
         mTestedSm.start();
         mLooper.dispatchAll();
         verify(mTetherHelper).notifyInterfaceStateChange(
@@ -274,4 +276,4 @@
                 upstreamIface);
         mLooper.dispatchAll();
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/jni/UidRangeTest.cpp b/tests/net/jni/UidRangeTest.cpp
similarity index 100%
rename from services/tests/servicestests/jni/UidRangeTest.cpp
rename to tests/net/jni/UidRangeTest.cpp
diff --git a/services/tests/servicestests/jni/UidRangeTest.h b/tests/net/jni/UidRangeTest.h
similarity index 100%
rename from services/tests/servicestests/jni/UidRangeTest.h
rename to tests/net/jni/UidRangeTest.h
diff --git a/services/tests/servicestests/jni/apf_jni.cpp b/tests/net/jni/apf_jni.cpp
similarity index 100%
rename from services/tests/servicestests/jni/apf_jni.cpp
rename to tests/net/jni/apf_jni.cpp
diff --git a/services/tests/servicestests/res/raw/apf.pcap b/tests/net/res/raw/apf.pcap
similarity index 100%
rename from services/tests/servicestests/res/raw/apf.pcap
rename to tests/net/res/raw/apf.pcap
Binary files differ
diff --git a/tests/utils/testutils/Android.mk b/tests/utils/testutils/Android.mk
index d53167f..acbe4bc 100644
--- a/tests/utils/testutils/Android.mk
+++ b/tests/utils/testutils/Android.mk
@@ -27,4 +27,6 @@
     android-support-test \
     mockito-target
 
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
similarity index 89%
rename from services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
rename to tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 13657ab..27b7419 100644
--- a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.internal.util.test;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -25,13 +25,12 @@
 import android.os.Handler;
 import android.os.UserHandle;
 
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.AbstractFuture;
-
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -42,9 +41,17 @@
 public class BroadcastInterceptingContext extends ContextWrapper {
     private static final String TAG = "WatchingContext";
 
-    private final List<BroadcastInterceptor> mInterceptors = Lists.newArrayList();
+    private final List<BroadcastInterceptor> mInterceptors = new ArrayList<>();
 
-    public class BroadcastInterceptor extends AbstractFuture<Intent> {
+    public abstract class FutureIntent extends FutureTask<Intent> {
+        public FutureIntent() {
+            super(
+                () -> { throw new IllegalStateException("Cannot happen"); }
+            );
+        }
+    }
+
+    public class BroadcastInterceptor extends FutureIntent {
         private final BroadcastReceiver mReceiver;
         private final IntentFilter mFilter;
 
@@ -82,11 +89,11 @@
         super(base);
     }
 
-    public Future<Intent> nextBroadcastIntent(String action) {
+    public FutureIntent nextBroadcastIntent(String action) {
         return nextBroadcastIntent(new IntentFilter(action));
     }
 
-    public Future<Intent> nextBroadcastIntent(IntentFilter filter) {
+    public FutureIntent nextBroadcastIntent(IntentFilter filter) {
         final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter);
         synchronized (mInterceptors) {
             mInterceptors.add(interceptor);
diff --git a/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java b/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java
rename to tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java
index 808f4dd..8ca849b 100644
--- a/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.util;
+package com.android.internal.util.test;
 
 import android.net.Uri;
 import android.os.Bundle;