Merge "Require location permission for ConnDiags WiFi only."
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 79c2b9d..5a08b23 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -146,7 +146,10 @@
     manifest: "AndroidManifest.xml",
     use_embedded_native_libs: true,
     // The permission configuration *must* be included to ensure security of the device
-    required: ["NetworkPermissionConfig"],
+    required: [
+        "NetworkPermissionConfig",
+        "privapp_whitelist_com.android.networkstack.tethering",
+    ],
     apex_available: ["com.android.tethering"],
     min_sdk_version: "30",
 }
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 917bf21..bd7ebda 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -41,8 +41,12 @@
         "ServiceConnectivityResources",
         "Tethering",
     ],
+    prebuilts: ["current_sdkinfo"],
     manifest: "manifest.json",
     key: "com.android.tethering.key",
+    // Indicates that pre-installed version of this apex can be compressed.
+    // Whether it actually will be compressed is controlled on per-device basis.
+    compressible: true,
 
     androidManifest: "AndroidManifest.xml",
 }
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index e47d802..4bc8f93 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -56,6 +56,8 @@
         "src/android/net/TetheringConfigurationParcel.aidl",
         "src/android/net/TetheringRequestParcel.aidl",
         "src/android/net/TetherStatesParcel.aidl",
+        "src/android/net/TetheringInterface.aidl",
+        "src/android/net/TetheringInterface.java",
     ],
     path: "src"
 }
diff --git a/Tethering/common/TetheringLib/api/module-lib-current.txt b/Tethering/common/TetheringLib/api/module-lib-current.txt
index 6ddb122..0566040 100644
--- a/Tethering/common/TetheringLib/api/module-lib-current.txt
+++ b/Tethering/common/TetheringLib/api/module-lib-current.txt
@@ -28,13 +28,13 @@
   }
 
   public static interface TetheringManager.TetheringEventCallback {
-    method public default void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
+    method @Deprecated public default void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
   }
 
-  public static class TetheringManager.TetheringInterfaceRegexps {
-    method @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
-    method @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
-    method @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
+  @Deprecated public static class TetheringManager.TetheringInterfaceRegexps {
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
   }
 
 }
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index 105bab1..844ff64 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -19,6 +19,15 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR;
   }
 
+  public final class TetheringInterface implements android.os.Parcelable {
+    ctor public TetheringInterface(int, @NonNull String);
+    method public int describeContents();
+    method @NonNull public String getInterface();
+    method public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheringInterface> CREATOR;
+  }
+
   public class TetheringManager {
     method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
@@ -26,7 +35,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
-    field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+    field @Deprecated public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
     field public static final int CONNECTIVITY_SCOPE_GLOBAL = 1; // 0x1
     field public static final int CONNECTIVITY_SCOPE_LOCAL = 2; // 0x2
     field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
@@ -74,10 +83,14 @@
   public static interface TetheringManager.TetheringEventCallback {
     method public default void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>);
     method public default void onError(@NonNull String, int);
+    method public default void onError(@NonNull android.net.TetheringInterface, int);
     method public default void onLocalOnlyInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public default void onLocalOnlyInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
     method public default void onOffloadStatusChanged(int);
     method public default void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public default void onTetherableInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
     method public default void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public default void onTetheredInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
     method public default void onTetheringSupported(boolean);
     method public default void onUpstreamChanged(@Nullable android.net.Network);
   }
diff --git a/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl
index 3d842b3..43262fb 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl
@@ -16,15 +16,17 @@
 
 package android.net;
 
+import android.net.TetheringInterface;
+
 /**
  * Status details for tethering downstream interfaces.
  * {@hide}
  */
 parcelable TetherStatesParcel {
-    String[] availableList;
-    String[] tetheredList;
-    String[] localOnlyList;
-    String[] erroredIfaceList;
+    TetheringInterface[] availableList;
+    TetheringInterface[] tetheredList;
+    TetheringInterface[] localOnlyList;
+    TetheringInterface[] erroredIfaceList;
     // List of Last error code corresponding to each errored iface in erroredIfaceList. */
     // TODO: Improve this as b/143122247.
     int[] lastErrorList;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringInterface.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.aidl
new file mode 100644
index 0000000..7151984
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2021 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;
+
+@JavaOnlyStableParcelable parcelable TetheringInterface;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
new file mode 100644
index 0000000..84cdef1
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.TetheringManager.TetheringType;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The mapping of tethering interface and type.
+ * @hide
+ */
+@SystemApi
+public final class TetheringInterface implements Parcelable {
+    private final int mType;
+    private final String mInterface;
+
+    public TetheringInterface(@TetheringType int type, @NonNull String iface) {
+        Objects.requireNonNull(iface);
+        mType = type;
+        mInterface = iface;
+    }
+
+    private TetheringInterface(@NonNull Parcel in) {
+        this(in.readInt(), in.readString());
+    }
+
+    /** Get tethering type. */
+    public int getType() {
+        return mType;
+    }
+
+    /** Get tethering interface. */
+    @NonNull
+    public String getInterface() {
+        return mInterface;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeString(mInterface);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mType, mInterface);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (!(obj instanceof TetheringInterface)) return false;
+        final TetheringInterface other = (TetheringInterface) obj;
+        return mType == other.mType && mInterface.equals(other.mInterface);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<TetheringInterface> CREATOR = new Creator<TetheringInterface>() {
+        @NonNull
+        @Override
+        public TetheringInterface createFromParcel(@NonNull Parcel in) {
+            return new TetheringInterface(in);
+        }
+
+        @NonNull
+        @Override
+        public TetheringInterface[] newArray(int size) {
+            return new TetheringInterface[size];
+        }
+    };
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "TetheringInterface {mType=" + mType
+                + ", mInterface=" + mInterface + "}";
+    }
+}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index c64da8a..73ab908 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -30,6 +30,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -43,6 +44,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
@@ -83,7 +85,10 @@
      * {@code TetheringManager.EXTRA_ERRORED_TETHER} to indicate
      * the current state of tethering.  Each include a list of
      * interface names in that state (may be empty).
+     *
+     * @deprecated New client should use TetheringEventCallback instead.
      */
+    @Deprecated
     public static final String ACTION_TETHER_STATE_CHANGED =
             "android.net.conn.TETHER_STATE_CHANGED";
 
@@ -531,7 +536,7 @@
     /**
      * Attempt to both alter the mode of USB and Tethering of USB.
      *
-     * @deprecated New client should not use this API anymore. All clients should use
+     * @deprecated New clients should not use this API anymore. All clients should use
      * #startTethering or #stopTethering which encapsulate proper entitlement logic. If the API is
      * used and an entitlement check is needed, downstream USB tethering will be enabled but will
      * not have any upstream.
@@ -978,8 +983,13 @@
          * multiple times later upon changes.
          * @param reg The new regular expressions.
          *
+         * @deprecated New clients should use the callbacks with {@link TetheringInterface} which
+         * has the mapping between tethering type and interface. InterfaceRegex is no longer needed
+         * to determine the mapping of tethering type and interface.
+         *
          * @hide
          */
+        @Deprecated
         @SystemApi(client = MODULE_LIBRARIES)
         default void onTetherableInterfaceRegexpsChanged(@NonNull TetheringInterfaceRegexps reg) {}
 
@@ -994,15 +1004,43 @@
         default void onTetherableInterfacesChanged(@NonNull List<String> interfaces) {}
 
         /**
+         * Called when there was a change in the list of tetherable interfaces. Tetherable
+         * interface means this interface is available and can be used for tethering.
+         *
+         * <p>This will be called immediately after the callback is registered, and may be called
+         * multiple times later upon changes.
+         * @param interfaces The set of TetheringInterface of currently tetherable interface.
+         */
+        default void onTetherableInterfacesChanged(@NonNull Set<TetheringInterface> interfaces) {
+            // By default, the new callback calls the old callback, so apps
+            // implementing the old callback just work.
+            onTetherableInterfacesChanged(toIfaces(interfaces));
+        }
+
+        /**
          * Called when there was a change in the list of tethered interfaces.
          *
          * <p>This will be called immediately after the callback is registered, and may be called
          * multiple times later upon changes.
-         * @param interfaces The list of 0 or more String of currently tethered interface names.
+         * @param interfaces The lit of 0 or more String of currently tethered interface names.
          */
         default void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {}
 
         /**
+         * Called when there was a change in the list of tethered interfaces.
+         *
+         * <p>This will be called immediately after the callback is registered, and may be called
+         * multiple times later upon changes.
+         * @param interfaces The set of 0 or more TetheringInterface of currently tethered
+         * interface.
+         */
+        default void onTetheredInterfacesChanged(@NonNull Set<TetheringInterface> interfaces) {
+            // By default, the new callback calls the old callback, so apps
+            // implementing the old callback just work.
+            onTetheredInterfacesChanged(toIfaces(interfaces));
+        }
+
+        /**
          * Called when there was a change in the list of local-only interfaces.
          *
          * <p>This will be called immediately after the callback is registered, and may be called
@@ -1012,6 +1050,20 @@
         default void onLocalOnlyInterfacesChanged(@NonNull List<String> interfaces) {}
 
         /**
+         * Called when there was a change in the list of local-only interfaces.
+         *
+         * <p>This will be called immediately after the callback is registered, and may be called
+         * multiple times later upon changes.
+         * @param interfaces The set of 0 or more TetheringInterface of active local-only
+         * interface.
+         */
+        default void onLocalOnlyInterfacesChanged(@NonNull Set<TetheringInterface> interfaces) {
+            // By default, the new callback calls the old callback, so apps
+            // implementing the old callback just work.
+            onLocalOnlyInterfacesChanged(toIfaces(interfaces));
+        }
+
+        /**
          * Called when an error occurred configuring tethering.
          *
          * <p>This will be called immediately after the callback is registered if the latest status
@@ -1022,6 +1074,20 @@
         default void onError(@NonNull String ifName, @TetheringIfaceError int error) {}
 
         /**
+         * Called when an error occurred configuring tethering.
+         *
+         * <p>This will be called immediately after the callback is registered if the latest status
+         * on the interface is an error, and may be called multiple times later upon changes.
+         * @param iface The interface that experienced the error.
+         * @param error One of {@code TetheringManager#TETHER_ERROR_*}.
+         */
+        default void onError(@NonNull TetheringInterface iface, @TetheringIfaceError int error) {
+            // By default, the new callback calls the old callback, so apps
+            // implementing the old callback just work.
+            onError(iface.getInterface(), error);
+        }
+
+        /**
          * Called when the list of tethered clients changes.
          *
          * <p>This callback provides best-effort information on connected clients based on state
@@ -1044,9 +1110,37 @@
     }
 
     /**
-     * Regular expressions used to identify tethering interfaces.
+     * Covert DownStreamInterface collection to interface String array list. Internal use only.
+     *
      * @hide
      */
+    public static ArrayList<String> toIfaces(Collection<TetheringInterface> tetherIfaces) {
+        final ArrayList<String> ifaces = new ArrayList<>();
+        for (TetheringInterface tether : tetherIfaces) {
+            ifaces.add(tether.getInterface());
+        }
+
+        return ifaces;
+    }
+
+    private static String[] toIfaces(TetheringInterface[] tetherIfaces) {
+        final String[] ifaces = new String[tetherIfaces.length];
+        for (int i = 0; i < tetherIfaces.length; i++) {
+            ifaces[i] = tetherIfaces[i].getInterface();
+        }
+
+        return ifaces;
+    }
+
+
+    /**
+     * Regular expressions used to identify tethering interfaces.
+     *
+     * @deprecated Instead of using regex to determine tethering type. New client could use the
+     * callbacks with {@link TetheringInterface} which has the mapping of type and interface.
+     * @hide
+     */
+    @Deprecated
     @SystemApi(client = MODULE_LIBRARIES)
     public static class TetheringInterfaceRegexps {
         private final String[] mTetherableBluetoothRegexs;
@@ -1114,10 +1208,10 @@
             }
             final ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() {
                 // Only accessed with a lock on this object
-                private final HashMap<String, Integer> mErrorStates = new HashMap<>();
-                private String[] mLastTetherableInterfaces = null;
-                private String[] mLastTetheredInterfaces = null;
-                private String[] mLastLocalOnlyInterfaces = null;
+                private final HashMap<TetheringInterface, Integer> mErrorStates = new HashMap<>();
+                private TetheringInterface[] mLastTetherableInterfaces = null;
+                private TetheringInterface[] mLastTetheredInterfaces = null;
+                private TetheringInterface[] mLastLocalOnlyInterfaces = null;
 
                 @Override
                 public void onUpstreamChanged(Network network) throws RemoteException {
@@ -1128,14 +1222,14 @@
 
                 private synchronized void sendErrorCallbacks(final TetherStatesParcel newStates) {
                     for (int i = 0; i < newStates.erroredIfaceList.length; i++) {
-                        final String iface = newStates.erroredIfaceList[i];
-                        final Integer lastError = mErrorStates.get(iface);
+                        final TetheringInterface tetherIface = newStates.erroredIfaceList[i];
+                        final Integer lastError = mErrorStates.get(tetherIface);
                         final int newError = newStates.lastErrorList[i];
                         if (newError != TETHER_ERROR_NO_ERROR
                                 && !Objects.equals(lastError, newError)) {
-                            callback.onError(iface, newError);
+                            callback.onError(tetherIface, newError);
                         }
-                        mErrorStates.put(iface, newError);
+                        mErrorStates.put(tetherIface, newError);
                     }
                 }
 
@@ -1144,7 +1238,7 @@
                     if (Arrays.equals(mLastTetherableInterfaces, newStates.availableList)) return;
                     mLastTetherableInterfaces = newStates.availableList.clone();
                     callback.onTetherableInterfacesChanged(
-                            Collections.unmodifiableList(Arrays.asList(mLastTetherableInterfaces)));
+                            Collections.unmodifiableSet((new ArraySet(mLastTetherableInterfaces))));
                 }
 
                 private synchronized void maybeSendTetheredIfacesChangedCallback(
@@ -1152,7 +1246,7 @@
                     if (Arrays.equals(mLastTetheredInterfaces, newStates.tetheredList)) return;
                     mLastTetheredInterfaces = newStates.tetheredList.clone();
                     callback.onTetheredInterfacesChanged(
-                            Collections.unmodifiableList(Arrays.asList(mLastTetheredInterfaces)));
+                            Collections.unmodifiableSet((new ArraySet(mLastTetheredInterfaces))));
                 }
 
                 private synchronized void maybeSendLocalOnlyIfacesChangedCallback(
@@ -1160,7 +1254,7 @@
                     if (Arrays.equals(mLastLocalOnlyInterfaces, newStates.localOnlyList)) return;
                     mLastLocalOnlyInterfaces = newStates.localOnlyList.clone();
                     callback.onLocalOnlyInterfacesChanged(
-                            Collections.unmodifiableList(Arrays.asList(mLastLocalOnlyInterfaces)));
+                            Collections.unmodifiableSet((new ArraySet(mLastLocalOnlyInterfaces))));
                 }
 
                 // Called immediately after the callbacks are registered.
@@ -1262,8 +1356,8 @@
         if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR;
 
         int i = 0;
-        for (String errored : mTetherStatesParcel.erroredIfaceList) {
-            if (iface.equals(errored)) return mTetherStatesParcel.lastErrorList[i];
+        for (TetheringInterface errored : mTetherStatesParcel.erroredIfaceList) {
+            if (iface.equals(errored.getInterface())) return mTetherStatesParcel.lastErrorList[i];
 
             i++;
         }
@@ -1327,7 +1421,7 @@
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return new String[0];
 
-        return mTetherStatesParcel.availableList;
+        return toIfaces(mTetherStatesParcel.availableList);
     }
 
     /**
@@ -1341,7 +1435,7 @@
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return new String[0];
 
-        return mTetherStatesParcel.tetheredList;
+        return toIfaces(mTetherStatesParcel.tetheredList);
     }
 
     /**
@@ -1361,7 +1455,7 @@
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return new String[0];
 
-        return mTetherStatesParcel.erroredIfaceList;
+        return toIfaces(mTetherStatesParcel.erroredIfaceList);
     }
 
     /**
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 0e8b2b5..7596380 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -51,6 +51,7 @@
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
+import static android.net.TetheringManager.toIfaces;
 import static android.net.util.TetheringMessageBase.BASE_MAIN_SM;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -92,6 +93,7 @@
 import android.net.TetheredClient;
 import android.net.TetheringCallbackStartedParcel;
 import android.net.TetheringConfigurationParcel;
+import android.net.TetheringInterface;
 import android.net.TetheringManager.TetheringRequest;
 import android.net.TetheringRequestParcel;
 import android.net.ip.IpServer;
@@ -143,7 +145,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -854,26 +855,27 @@
     private void sendTetherStateChangedBroadcast() {
         if (!isTetheringSupported()) return;
 
-        final ArrayList<String> availableList = new ArrayList<>();
-        final ArrayList<String> tetherList = new ArrayList<>();
-        final ArrayList<String> localOnlyList = new ArrayList<>();
-        final ArrayList<String> erroredList = new ArrayList<>();
-        final ArrayList<Integer> lastErrorList = new ArrayList<>();
+        final ArrayList<TetheringInterface> available = new ArrayList<>();
+        final ArrayList<TetheringInterface> tethered = new ArrayList<>();
+        final ArrayList<TetheringInterface> localOnly = new ArrayList<>();
+        final ArrayList<TetheringInterface> errored = new ArrayList<>();
+        final ArrayList<Integer> lastErrors = new ArrayList<>();
 
         final TetheringConfiguration cfg = mConfig;
-        mTetherStatesParcel = new TetherStatesParcel();
 
         int downstreamTypesMask = DOWNSTREAM_NONE;
         for (int i = 0; i < mTetherStates.size(); i++) {
-            TetherState tetherState = mTetherStates.valueAt(i);
-            String iface = mTetherStates.keyAt(i);
+            final TetherState tetherState = mTetherStates.valueAt(i);
+            final int type = tetherState.ipServer.interfaceType();
+            final String iface = mTetherStates.keyAt(i);
+            final TetheringInterface tetheringIface = new TetheringInterface(type, iface);
             if (tetherState.lastError != TETHER_ERROR_NO_ERROR) {
-                erroredList.add(iface);
-                lastErrorList.add(tetherState.lastError);
+                errored.add(tetheringIface);
+                lastErrors.add(tetherState.lastError);
             } else if (tetherState.lastState == IpServer.STATE_AVAILABLE) {
-                availableList.add(iface);
+                available.add(tetheringIface);
             } else if (tetherState.lastState == IpServer.STATE_LOCAL_ONLY) {
-                localOnlyList.add(iface);
+                localOnly.add(tetheringIface);
             } else if (tetherState.lastState == IpServer.STATE_TETHERED) {
                 if (cfg.isUsb(iface)) {
                     downstreamTypesMask |= (1 << TETHERING_USB);
@@ -882,40 +884,63 @@
                 } else if (cfg.isBluetooth(iface)) {
                     downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
                 }
-                tetherList.add(iface);
+                tethered.add(tetheringIface);
             }
         }
 
-        mTetherStatesParcel.availableList = availableList.toArray(new String[0]);
-        mTetherStatesParcel.tetheredList = tetherList.toArray(new String[0]);
-        mTetherStatesParcel.localOnlyList = localOnlyList.toArray(new String[0]);
-        mTetherStatesParcel.erroredIfaceList = erroredList.toArray(new String[0]);
-        mTetherStatesParcel.lastErrorList = new int[lastErrorList.size()];
-        Iterator<Integer> iterator = lastErrorList.iterator();
-        for (int i = 0; i < lastErrorList.size(); i++) {
-            mTetherStatesParcel.lastErrorList[i] = iterator.next().intValue();
-        }
+        mTetherStatesParcel = buildTetherStatesParcel(available, localOnly, tethered, errored,
+                lastErrors);
         reportTetherStateChanged(mTetherStatesParcel);
 
-        final Intent bcast = new Intent(ACTION_TETHER_STATE_CHANGED);
-        bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        bcast.putStringArrayListExtra(EXTRA_AVAILABLE_TETHER, availableList);
-        bcast.putStringArrayListExtra(EXTRA_ACTIVE_LOCAL_ONLY, localOnlyList);
-        bcast.putStringArrayListExtra(EXTRA_ACTIVE_TETHER, tetherList);
-        bcast.putStringArrayListExtra(EXTRA_ERRORED_TETHER, erroredList);
-        mContext.sendStickyBroadcastAsUser(bcast, UserHandle.ALL);
+        mContext.sendStickyBroadcastAsUser(buildStateChangeIntent(available, localOnly, tethered,
+                errored), UserHandle.ALL);
         if (DBG) {
             Log.d(TAG, String.format(
-                    "sendTetherStateChangedBroadcast %s=[%s] %s=[%s] %s=[%s] %s=[%s]",
-                    "avail", TextUtils.join(",", availableList),
-                    "local_only", TextUtils.join(",", localOnlyList),
-                    "tether", TextUtils.join(",", tetherList),
-                    "error", TextUtils.join(",", erroredList)));
+                    "reportTetherStateChanged %s=[%s] %s=[%s] %s=[%s] %s=[%s]",
+                    "avail", TextUtils.join(",", available),
+                    "local_only", TextUtils.join(",", localOnly),
+                    "tether", TextUtils.join(",", tethered),
+                    "error", TextUtils.join(",", errored)));
         }
 
         mNotificationUpdater.onDownstreamChanged(downstreamTypesMask);
     }
 
+    private TetherStatesParcel buildTetherStatesParcel(
+            final ArrayList<TetheringInterface> available,
+            final ArrayList<TetheringInterface> localOnly,
+            final ArrayList<TetheringInterface> tethered,
+            final ArrayList<TetheringInterface> errored,
+            final ArrayList<Integer> lastErrors) {
+        final TetherStatesParcel parcel = new TetherStatesParcel();
+
+        parcel.availableList = available.toArray(new TetheringInterface[0]);
+        parcel.tetheredList = tethered.toArray(new TetheringInterface[0]);
+        parcel.localOnlyList = localOnly.toArray(new TetheringInterface[0]);
+        parcel.erroredIfaceList = errored.toArray(new TetheringInterface[0]);
+        parcel.lastErrorList = new int[lastErrors.size()];
+        for (int i = 0; i < lastErrors.size(); i++) {
+            parcel.lastErrorList[i] = lastErrors.get(i);
+        }
+
+        return parcel;
+    }
+
+    private Intent buildStateChangeIntent(final ArrayList<TetheringInterface> available,
+            final ArrayList<TetheringInterface> localOnly,
+            final ArrayList<TetheringInterface> tethered,
+            final ArrayList<TetheringInterface> errored) {
+        final Intent bcast = new Intent(ACTION_TETHER_STATE_CHANGED);
+        bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+        bcast.putStringArrayListExtra(EXTRA_AVAILABLE_TETHER, toIfaces(available));
+        bcast.putStringArrayListExtra(EXTRA_ACTIVE_LOCAL_ONLY, toIfaces(localOnly));
+        bcast.putStringArrayListExtra(EXTRA_ACTIVE_TETHER, toIfaces(tethered));
+        bcast.putStringArrayListExtra(EXTRA_ERRORED_TETHER, toIfaces(errored));
+
+        return bcast;
+    }
+
     private class StateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context content, Intent intent) {
@@ -2082,10 +2107,10 @@
 
     private TetherStatesParcel emptyTetherStatesParcel() {
         final TetherStatesParcel parcel = new TetherStatesParcel();
-        parcel.availableList = new String[0];
-        parcel.tetheredList = new String[0];
-        parcel.localOnlyList = new String[0];
-        parcel.erroredIfaceList = new String[0];
+        parcel.availableList = new TetheringInterface[0];
+        parcel.tetheredList = new TetheringInterface[0];
+        parcel.localOnlyList = new TetheringInterface[0];
+        parcel.erroredIfaceList = new TetheringInterface[0];
         parcel.lastErrorList = new int[0];
 
         return parcel;
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index de94cba..f1ddc6d 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -80,6 +80,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Random;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -349,7 +350,7 @@
         private final CountDownLatch mLocalOnlyStartedLatch = new CountDownLatch(1);
         private final CountDownLatch mLocalOnlyStoppedLatch = new CountDownLatch(1);
         private final CountDownLatch mClientConnectedLatch = new CountDownLatch(1);
-        private final String mIface;
+        private final TetheringInterface mIface;
 
         private volatile boolean mInterfaceWasTethered = false;
         private volatile boolean mInterfaceWasLocalOnly = false;
@@ -358,20 +359,24 @@
 
         MyTetheringEventCallback(TetheringManager tm, String iface) {
             mTm = tm;
-            mIface = iface;
+            mIface = new TetheringInterface(TETHERING_ETHERNET, iface);
         }
 
         public void unregister() {
             mTm.unregisterTetheringEventCallback(this);
             mUnregistered = true;
         }
-
         @Override
         public void onTetheredInterfacesChanged(List<String> interfaces) {
+            fail("Should only call callback that takes a Set<TetheringInterface>");
+        }
+
+        @Override
+        public void onTetheredInterfacesChanged(Set<TetheringInterface> interfaces) {
             // Ignore stale callbacks registered by previous test cases.
             if (mUnregistered) return;
 
-            if (!mInterfaceWasTethered && (mIface == null || interfaces.contains(mIface))) {
+            if (!mInterfaceWasTethered && interfaces.contains(mIface)) {
                 // This interface is being tethered for the first time.
                 Log.d(TAG, "Tethering started: " + interfaces);
                 mInterfaceWasTethered = true;
@@ -384,10 +389,15 @@
 
         @Override
         public void onLocalOnlyInterfacesChanged(List<String> interfaces) {
+            fail("Should only call callback that takes a Set<TetheringInterface>");
+        }
+
+        @Override
+        public void onLocalOnlyInterfacesChanged(Set<TetheringInterface> interfaces) {
             // Ignore stale callbacks registered by previous test cases.
             if (mUnregistered) return;
 
-            if (!mInterfaceWasLocalOnly && (mIface == null || interfaces.contains(mIface))) {
+            if (!mInterfaceWasLocalOnly && interfaces.contains(mIface)) {
                 // This interface is being put into local-only mode for the first time.
                 Log.d(TAG, "Local-only started: " + interfaces);
                 mInterfaceWasLocalOnly = true;
diff --git a/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java b/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java
index 7ffe37a..07aab63 100644
--- a/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java
+++ b/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java
@@ -15,17 +15,18 @@
  */
 package android.tethering.mts;
 
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.READ_DEVICE_CONFIG;
 import static android.Manifest.permission.TETHER_PRIVILEGED;
 import static android.Manifest.permission.WRITE_SETTINGS;
+import static android.net.TetheringManager.TETHERING_WIFI;
 import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
 import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
@@ -35,6 +36,7 @@
 import android.content.Context;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
+import android.net.TetheringInterface;
 import android.net.TetheringManager;
 import android.net.cts.util.CtsTetheringUtils;
 import android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
@@ -69,7 +71,7 @@
     @Before
     public void setUp() throws Exception {
         mUiAutomation.adoptShellPermissionIdentity(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS,
-                WRITE_SETTINGS, READ_DEVICE_CONFIG, TETHER_PRIVILEGED);
+                WRITE_SETTINGS, READ_DEVICE_CONFIG, TETHER_PRIVILEGED, ACCESS_WIFI_STATE);
         mContext = InstrumentationRegistry.getContext();
         mTm = mContext.getSystemService(TetheringManager.class);
         mCtsTetheringUtils = new CtsTetheringUtils(mContext);
@@ -101,13 +103,14 @@
         TestNetworkTracker tnt = null;
         try {
             tetherEventCallback.assumeTetheringSupported();
-            assumeTrue(isWifiTetheringSupported(tetherEventCallback));
+            assumeTrue(isWifiTetheringSupported(mContext, tetherEventCallback));
+            tetherEventCallback.expectNoTetheringActive();
 
-            mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
+            final TetheringInterface tetheredIface =
+                    mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
 
-            final List<String> tetheredIfaces = tetherEventCallback.getTetheredInterfaces();
-            assertEquals(1, tetheredIfaces.size());
-            final String wifiTetheringIface = tetheredIfaces.get(0);
+            assertNotNull(tetheredIface);
+            final String wifiTetheringIface = tetheredIface.getInterface();
 
             NetworkInterface nif = NetworkInterface.getByName(wifiTetheringIface);
             // Tethering downstream only have one ipv4 address.
@@ -120,11 +123,11 @@
             tnt = setUpTestNetwork(
                     new LinkAddress(testPrefix.getAddress(), testPrefix.getPrefixLength()));
 
-            tetherEventCallback.expectTetheredInterfacesChanged(null);
+            tetherEventCallback.expectNoTetheringActive();
             final List<String> wifiRegexs =
                     tetherEventCallback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
 
-            tetherEventCallback.expectTetheredInterfacesChanged(wifiRegexs);
+            tetherEventCallback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI);
             nif = NetworkInterface.getByName(wifiTetheringIface);
             final LinkAddress newHotspotAddr = getFirstIpv4Address(nif);
             assertNotNull(newHotspotAddr);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 94a9238..5235eb7 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -130,6 +130,7 @@
 import android.net.TetheredClient.AddressInfo;
 import android.net.TetheringCallbackStartedParcel;
 import android.net.TetheringConfigurationParcel;
+import android.net.TetheringInterface;
 import android.net.TetheringRequestParcel;
 import android.net.dhcp.DhcpLeaseParcelable;
 import android.net.dhcp.DhcpServerCallbacks;
@@ -1793,6 +1794,8 @@
     public void testRegisterTetheringEventCallback() throws Exception {
         TestTetheringEventCallback callback = new TestTetheringEventCallback();
         TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
+        final TetheringInterface wifiIface = new TetheringInterface(
+                TETHERING_WIFI, TEST_WLAN_IFNAME);
 
         // 1. Register one callback before running any tethering.
         mTethering.registerTetheringEventCallback(callback);
@@ -1811,12 +1814,12 @@
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
         mLooper.dispatchAll();
         tetherState = callback.pollTetherStatesChanged();
-        assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
+        assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
 
         mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
         sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
         tetherState = callback.pollTetherStatesChanged();
-        assertArrayEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
+        assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIface});
         callback.expectUpstreamChanged(upstreamState.network);
         callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED);
 
@@ -1828,7 +1831,7 @@
         callback2.expectConfigurationChanged(
                 mTethering.getTetheringConfiguration().toStableParcelable());
         tetherState = callback2.pollTetherStatesChanged();
-        assertEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
+        assertEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIface});
         callback2.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED);
 
         // 4. Unregister first callback and disable wifi tethering
@@ -1837,7 +1840,7 @@
         mTethering.stopTethering(TETHERING_WIFI);
         sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
         tetherState = callback2.pollTetherStatesChanged();
-        assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
+        assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
         mLooper.dispatchAll();
         callback2.expectUpstreamChanged(new Network[] {null});
         callback2.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
diff --git a/framework/Android.bp b/framework/Android.bp
index c91017f..4fa9ccb 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -108,10 +108,8 @@
         "-Wthread-safety",
     ],
     shared_libs: [
-        "libbase",
         "liblog",
         "libnativehelper",
-        "libnetd_client",
     ],
     header_libs: [
         "dnsproxyd_protocol_headers",
@@ -124,6 +122,7 @@
     srcs: [
         "jni/android_net_NetworkUtils.cpp",
     ],
+    shared_libs: ["libandroid_net"],
     apex_available: [
         "//apex_available:platform",
         "com.android.tethering",
@@ -134,9 +133,11 @@
     name: "libframework-connectivity-jni",
     defaults: ["libframework-connectivity-defaults"],
     srcs: [
+        "jni/android_net_NetworkUtils.cpp",
         "jni/onload.cpp",
     ],
-    static_libs: ["libconnectivityframeworkutils"],
+    shared_libs: ["libandroid"],
+    stl: "libc++_static",
     apex_available: [
         "//apex_available:platform",
         "com.android.tethering",
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 6c454bc..7fc0382 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -48,7 +48,6 @@
 
   public class ConnectivitySettingsManager {
     method public static void clearGlobalProxy(@NonNull android.content.Context);
-    method @NonNull public static java.util.Set<java.lang.String> getAppsAllowedOnRestrictedNetworks(@NonNull android.content.Context);
     method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context);
     method public static int getCaptivePortalMode(@NonNull android.content.Context, int);
     method @NonNull public static java.time.Duration getConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
@@ -66,9 +65,9 @@
     method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context);
     method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context);
     method public static int getPrivateDnsMode(@NonNull android.content.Context);
+    method @NonNull public static java.util.Set<java.lang.Integer> getUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context);
     method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean);
     method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
-    method public static void setAppsAllowedOnRestrictedNetworks(@NonNull android.content.Context, @NonNull java.util.Set<java.lang.String>);
     method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String);
     method public static void setCaptivePortalMode(@NonNull android.content.Context, int);
     method public static void setConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
@@ -86,6 +85,7 @@
     method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull int);
     method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String);
     method public static void setPrivateDnsMode(@NonNull android.content.Context, int);
+    method public static void setUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context, @NonNull java.util.Set<java.lang.Integer>);
     method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean);
     method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
     field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 27bf114..27836c1 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -316,9 +316,16 @@
     method public int getProviderId();
     method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest);
     method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void registerNetworkOffer(@NonNull android.net.NetworkScore, @NonNull android.net.NetworkCapabilities, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkProvider.NetworkOfferCallback);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkOffer(@NonNull android.net.NetworkProvider.NetworkOfferCallback);
     field public static final int ID_NONE = -1; // 0xffffffff
   }
 
+  public static interface NetworkProvider.NetworkOfferCallback {
+    method public void onNetworkNeeded(@NonNull android.net.NetworkRequest);
+    method public void onNetworkUnneeded(@NonNull android.net.NetworkRequest);
+  }
+
   public class NetworkReleasedException extends java.lang.Exception {
   }
 
@@ -334,15 +341,23 @@
 
   public final class NetworkScore implements android.os.Parcelable {
     method public int describeContents();
+    method public int getKeepConnectedReason();
     method public int getLegacyInt();
+    method public boolean isExiting();
+    method public boolean isTransportPrimary();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR;
+    field public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; // 0x1
+    field public static final int KEEP_CONNECTED_NONE = 0; // 0x0
   }
 
   public static final class NetworkScore.Builder {
     ctor public NetworkScore.Builder();
     method @NonNull public android.net.NetworkScore build();
+    method @NonNull public android.net.NetworkScore.Builder setExiting(boolean);
+    method @NonNull public android.net.NetworkScore.Builder setKeepConnectedReason(int);
     method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int);
+    method @NonNull public android.net.NetworkScore.Builder setTransportPrimary(boolean);
   }
 
   public final class OemNetworkPreferences implements android.os.Parcelable {
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index f17baf9..26484fb 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
 #include <arpa/inet.h>
 #include <linux/filter.h>
 #include <linux/if_arp.h>
@@ -36,7 +37,6 @@
 #include <utils/Log.h>
 #include <utils/misc.h>
 
-#include "NetdClient.h"
 #include "jni.h"
 
 extern "C" {
@@ -94,25 +94,32 @@
     }
 }
 
-static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
+static jboolean android_net_utils_bindProcessToNetworkHandle(JNIEnv *env, jobject thiz,
+        jlong netHandle)
 {
-    return (jboolean) !setNetworkForProcess(netId);
+    return (jboolean) !android_setprocnetwork(netHandle);
 }
 
-static jint android_net_utils_getBoundNetworkForProcess(JNIEnv *env, jobject thiz)
+static jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jobject thiz)
 {
-    return getNetworkForProcess();
+    net_handle_t network;
+    if (android_getprocnetwork(&network) != 0) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                "android_getprocnetwork(): %s", strerror(errno));
+        return NETWORK_UNSPECIFIED;
+    }
+    return (jlong) network;
 }
 
 static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
-        jint netId)
+        jint netId, jlong netHandle)
 {
-    return (jboolean) !setNetworkForResolv(netId);
+    return (jboolean) !android_setprocdns(netHandle);
 }
 
-static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd,
-                                                  jint netId) {
-    return setNetworkForSocket(netId, AFileDescriptor_getFd(env, javaFd));
+static jint android_net_utils_bindSocketToNetworkHandle(JNIEnv *env, jobject thiz, jobject javaFd,
+                                                  jlong netHandle) {
+    return android_setsocknetwork(netHandle, AFileDescriptor_getFd(env, javaFd));
 }
 
 static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
@@ -124,7 +131,7 @@
     return true;
 }
 
-static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
+static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jlong netHandle,
         jstring dname, jint ns_class, jint ns_type, jint flags) {
     const jsize javaCharsCount = env->GetStringLength(dname);
     const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
@@ -134,7 +141,8 @@
     std::vector<char> queryname(byteCountUTF8 + 1, 0);
 
     env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
-    int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
+
+    int fd = android_res_nquery(netHandle, queryname.data(), ns_class, ns_type, flags);
 
     if (fd < 0) {
         jniThrowErrnoException(env, "resNetworkQuery", -fd);
@@ -144,12 +152,12 @@
     return jniCreateFileDescriptor(env, fd);
 }
 
-static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId,
+static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jlong netHandle,
         jbyteArray msg, jint msgLen, jint flags) {
     uint8_t data[MAXCMDSIZE];
 
     checkLenAndCopy(env, msg, msgLen, data);
-    int fd = resNetworkSend(netId, data, msgLen, flags);
+    int fd = android_res_nsend(netHandle, data, msgLen, flags);
 
     if (fd < 0) {
         jniThrowErrnoException(env, "resNetworkSend", -fd);
@@ -164,7 +172,7 @@
     int rcode;
     std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
 
-    int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
+    int res = android_res_nresult(fd, &rcode, buf.data(), MAXPACKETSIZE);
     jniSetFileDescriptorOfFD(env, javaFd, -1);
     if (res < 0) {
         jniThrowErrnoException(env, "resNetworkResult", -res);
@@ -188,23 +196,22 @@
 
 static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
     int fd = AFileDescriptor_getFd(env, javaFd);
-    resNetworkCancel(fd);
+    android_res_cancel(fd);
     jniSetFileDescriptorOfFD(env, javaFd, -1);
 }
 
 static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
-    unsigned dnsNetId = 0;
-    if (int res = getNetworkForDns(&dnsNetId) < 0) {
-        jniThrowErrnoException(env, "getDnsNetId", -res);
+    net_handle_t dnsNetHandle = NETWORK_UNSPECIFIED;
+    if (int res = android_getprocdns(&dnsNetHandle) < 0) {
+        jniThrowErrnoException(env, "getDnsNetwork", -res);
         return nullptr;
     }
-    bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS;
 
     static jclass class_Network = MakeGlobalRefOrDie(
             env, FindClassOrDie(env, "android/net/Network"));
-    static jmethodID ctor = env->GetMethodID(class_Network, "<init>", "(IZ)V");
-    return env->NewObject(
-            class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass);
+    static jmethodID method = env->GetStaticMethodID(class_Network, "fromNetworkHandle",
+            "(J)Landroid/net/Network;");
+    return env->CallStaticObjectMethod(class_Network, method, static_cast<jlong>(dnsNetHandle));
 }
 
 static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
@@ -250,15 +257,15 @@
 // clang-format off
 static const JNINativeMethod gNetworkUtilMethods[] = {
     /* name, signature, funcPtr */
-    { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
-    { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
+    { "bindProcessToNetworkHandle", "(J)Z", (void*) android_net_utils_bindProcessToNetworkHandle },
+    { "getBoundNetworkHandleForProcess", "()J", (void*) android_net_utils_getBoundNetworkHandleForProcess },
     { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
-    { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
+    { "bindSocketToNetworkHandle", "(Ljava/io/FileDescriptor;J)I", (void*) android_net_utils_bindSocketToNetworkHandle },
     { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
     { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
     { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
-    { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
-    { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
+    { "resNetworkSend", "(J[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
+    { "resNetworkQuery", "(JLjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
     { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
     { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
     { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork },
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 1a6b37b..20f3853 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -3341,9 +3341,10 @@
      * Register or update a network offer with ConnectivityService.
      *
      * ConnectivityService keeps track of offers made by the various providers and matches
-     * them to networking requests made by apps or the system. The provider supplies a score
-     * and the capabilities of the network it might be able to bring up ; these act as filters
-     * used by ConnectivityService to only send those requests that can be fulfilled by the
+     * them to networking requests made by apps or the system. A callback identifies an offer
+     * uniquely, and later calls with the same callback update the offer. The provider supplies a
+     * score and the capabilities of the network it might be able to bring up ; these act as
+     * filters used by ConnectivityService to only send those requests that can be fulfilled by the
      * provider.
      *
      * The provider is under no obligation to be able to bring up the network it offers at any
@@ -3359,16 +3360,16 @@
      * @param score The prospective score of the network.
      * @param caps The prospective capabilities of the network.
      * @param callback The callback to call when this offer is needed or unneeded.
-     * @hide
+     * @hide exposed via the NetworkProvider class.
      */
     @RequiresPermission(anyOf = {
             NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
             android.Manifest.permission.NETWORK_FACTORY})
-    public void offerNetwork(@NonNull final NetworkProvider provider,
+    public void offerNetwork(@NonNull final int providerId,
             @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps,
             @NonNull final INetworkOfferCallback callback) {
         try {
-            mService.offerNetwork(Objects.requireNonNull(provider.getMessenger(), "null messenger"),
+            mService.offerNetwork(providerId,
                     Objects.requireNonNull(score, "null score"),
                     Objects.requireNonNull(caps, "null caps"),
                     Objects.requireNonNull(callback, "null callback"));
@@ -3382,7 +3383,7 @@
      *
      * @param callback The callback passed at registration time. This must be the same object
      *                 that was passed to {@link #offerNetwork}
-     * @hide
+     * @hide exposed via the NetworkProvider class.
      */
     public void unofferNetwork(@NonNull final INetworkOfferCallback callback) {
         try {
@@ -4916,7 +4917,7 @@
             InetAddressCompat.clearDnsCache();
             // Must flush socket pool as idle sockets will be bound to previous network and may
             // cause subsequent fetches to be performed on old network.
-            NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
+            NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();
         }
 
         return true;
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index 762f24f..5f7f539 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -374,12 +374,12 @@
     private static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING = "hostname";
 
     /**
-     * A list of apps that is allowed on restricted networks.
+     * A list of uids that is allowed to use restricted networks.
      *
      * @hide
      */
-    public static final String APPS_ALLOWED_ON_RESTRICTED_NETWORKS =
-            "apps_allowed_on_restricted_networks";
+    public static final String UIDS_ALLOWED_ON_RESTRICTED_NETWORKS =
+            "uids_allowed_on_restricted_networks";
 
     /**
      * Get mobile data activity timeout from {@link Settings}.
@@ -1003,6 +1003,28 @@
                 context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE, preference);
     }
 
+    private static Set<Integer> getUidSetFromString(@Nullable String uidList) {
+        final Set<Integer> uids = new ArraySet<>();
+        if (TextUtils.isEmpty(uidList)) {
+            return uids;
+        }
+        for (String uid : uidList.split(";")) {
+            uids.add(Integer.valueOf(uid));
+        }
+        return uids;
+    }
+
+    private static String getUidStringFromSet(@NonNull Set<Integer> uidList) {
+        final StringJoiner joiner = new StringJoiner(";");
+        for (Integer uid : uidList) {
+            if (uid < 0 || UserHandle.getAppId(uid) > Process.LAST_APPLICATION_UID) {
+                throw new IllegalArgumentException("Invalid uid");
+            }
+            joiner.add(uid.toString());
+        }
+        return joiner.toString();
+    }
+
     /**
      * Get the list of uids(from {@link Settings}) that should go on cellular networks in preference
      * even when higher-priority networks are connected.
@@ -1015,14 +1037,7 @@
     public static Set<Integer> getMobileDataPreferredUids(@NonNull Context context) {
         final String uidList = Settings.Secure.getString(
                 context.getContentResolver(), MOBILE_DATA_PREFERRED_UIDS);
-        final Set<Integer> uids = new ArraySet<>();
-        if (TextUtils.isEmpty(uidList)) {
-            return uids;
-        }
-        for (String uid : uidList.split(";")) {
-            uids.add(Integer.valueOf(uid));
-        }
-        return uids;
+        return getUidSetFromString(uidList);
     }
 
     /**
@@ -1035,53 +1050,41 @@
      */
     public static void setMobileDataPreferredUids(@NonNull Context context,
             @NonNull Set<Integer> uidList) {
-        final StringJoiner joiner = new StringJoiner(";");
-        for (Integer uid : uidList) {
-            if (uid < 0 || UserHandle.getAppId(uid) > Process.LAST_APPLICATION_UID) {
-                throw new IllegalArgumentException("Invalid uid");
-            }
-            joiner.add(uid.toString());
-        }
-        Settings.Secure.putString(
-                context.getContentResolver(), MOBILE_DATA_PREFERRED_UIDS, joiner.toString());
+        final String uids = getUidStringFromSet(uidList);
+        Settings.Secure.putString(context.getContentResolver(), MOBILE_DATA_PREFERRED_UIDS, uids);
     }
 
     /**
-     * Get the list of apps(from {@link Settings}) that is allowed on restricted networks.
+     * Get the list of uids (from {@link Settings}) allowed to use restricted networks.
+     *
+     * Access to restricted networks is controlled by the (preinstalled-only)
+     * CONNECTIVITY_USE_RESTRICTED_NETWORKS permission, but highly privileged
+     * callers can also set a list of uids that can access restricted networks.
+     *
+     * This is useful for example in some jurisdictions where government apps,
+     * that can't be preinstalled, must still have access to emergency services.
      *
      * @param context The {@link Context} to query the setting.
-     * @return A list of apps that is allowed on restricted networks or null if no setting
+     * @return A list of uids that is allowed to use restricted networks or null if no setting
      *         value.
      */
     @NonNull
-    public static Set<String> getAppsAllowedOnRestrictedNetworks(@NonNull Context context) {
-        final String appList = Settings.Secure.getString(
-                context.getContentResolver(), APPS_ALLOWED_ON_RESTRICTED_NETWORKS);
-        if (TextUtils.isEmpty(appList)) {
-            return new ArraySet<>();
-        }
-        return new ArraySet<>(appList.split(";"));
+    public static Set<Integer> getUidsAllowedOnRestrictedNetworks(@NonNull Context context) {
+        final String uidList = Settings.Secure.getString(
+                context.getContentResolver(), UIDS_ALLOWED_ON_RESTRICTED_NETWORKS);
+        return getUidSetFromString(uidList);
     }
 
     /**
-     * Set the list of apps(from {@link Settings}) that is allowed on restricted networks.
-     *
-     * Note: Please refer to android developer guidelines for valid app(package name).
-     * https://developer.android.com/guide/topics/manifest/manifest-element.html#package
+     * Set the list of uids(from {@link Settings}) that is allowed to use restricted networks.
      *
      * @param context The {@link Context} to set the setting.
-     * @param list A list of apps that is allowed on restricted networks.
+     * @param uidList A list of uids that is allowed to use restricted networks.
      */
-    public static void setAppsAllowedOnRestrictedNetworks(@NonNull Context context,
-            @NonNull Set<String> list) {
-        final StringJoiner joiner = new StringJoiner(";");
-        for (String app : list) {
-            if (app == null || app.contains(";")) {
-                throw new IllegalArgumentException("Invalid app(package name)");
-            }
-            joiner.add(app);
-        }
-        Settings.Secure.putString(context.getContentResolver(), APPS_ALLOWED_ON_RESTRICTED_NETWORKS,
-                joiner.toString());
+    public static void setUidsAllowedOnRestrictedNetworks(@NonNull Context context,
+            @NonNull Set<Integer> uidList) {
+        final String uids = getUidStringFromSet(uidList);
+        Settings.Secure.putString(context.getContentResolver(), UIDS_ALLOWED_ON_RESTRICTED_NETWORKS,
+                uids);
     }
 }
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index d937c9c..c434bbc 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -223,7 +223,7 @@
 
     int getRestrictBackgroundStatusByCaller();
 
-    void offerNetwork(in Messenger messenger, in NetworkScore score,
+    void offerNetwork(int providerId, in NetworkScore score,
             in NetworkCapabilities caps, in INetworkOfferCallback callback);
     void unofferNetwork(in INetworkOfferCallback callback);
 }
diff --git a/framework/src/android/net/INetworkOfferCallback.aidl b/framework/src/android/net/INetworkOfferCallback.aidl
index 67d2d40..ecfba21 100644
--- a/framework/src/android/net/INetworkOfferCallback.aidl
+++ b/framework/src/android/net/INetworkOfferCallback.aidl
@@ -22,15 +22,16 @@
  * A callback registered with connectivity by network providers together with
  * a NetworkOffer.
  *
- * When the offer is needed to satisfy some application or system component,
- * connectivity will call onOfferNeeded on this callback. When this happens,
- * the provider should try and bring up the network.
+ * When the network for this offer is needed to satisfy some application or
+ * system component, connectivity will call onNetworkNeeded on this callback.
+ * When this happens, the provider should try and bring up the network.
  *
- * When the offer is no longer needed, for example because the application has
- * withdrawn the request or if the request is being satisfied by a network
- * that this offer will never be able to beat, connectivity calls
- * onOfferUnneeded. When this happens, the provider should stop trying to
- * bring up the network, or tear it down if it has already been brought up.
+ * When the network for this offer is no longer needed, for example because
+ * the application has withdrawn the request or if the request is being
+ * satisfied by a network that this offer will never be able to beat,
+ * connectivity calls onNetworkUnneeded. When this happens, the provider
+ * should stop trying to bring up the network, or tear it down if it has
+ * already been brought up.
  *
  * When NetworkProvider#offerNetwork is called, the provider can expect to
  * immediately receive all requests that can be fulfilled by that offer and
@@ -38,25 +39,23 @@
  * request is currently outstanding, because no requests have been made that
  * can be satisfied by this offer, or because all such requests are already
  * satisfied by a better network.
- * onOfferNeeded can be called at any time after registration and until the
+ * onNetworkNeeded can be called at any time after registration and until the
  * offer is withdrawn with NetworkProvider#unofferNetwork is called. This
  * typically happens when a new network request is filed by an application,
  * or when the network satisfying a request disconnects and this offer now
- * stands a chance to be the best network for it.
+ * stands a chance to supply the best network for it.
  *
  * @hide
  */
 oneway interface INetworkOfferCallback {
     /**
-     * Informs the registrant that the offer is needed to fulfill this request.
+     * Called when a network for this offer is needed to fulfill this request.
      * @param networkRequest the request to satisfy
-     * @param providerId the ID of the provider currently satisfying
-     *          this request, or NetworkProvider.ID_NONE if none.
      */
-    void onOfferNeeded(in NetworkRequest networkRequest, int providerId);
+    void onNetworkNeeded(in NetworkRequest networkRequest);
 
     /**
-     * Informs the registrant that the offer is no longer needed to fulfill this request.
+     * Informs the registrant that the offer is no longer valuable to fulfill this request.
      */
-    void onOfferUnneeded(in NetworkRequest networkRequest);
+    void onNetworkUnneeded(in NetworkRequest networkRequest);
 }
diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java
index 41fad63..1f49033 100644
--- a/framework/src/android/net/Network.java
+++ b/framework/src/android/net/Network.java
@@ -92,6 +92,7 @@
     // value in the native/android/net.c NDK implementation.
     private static final long HANDLE_MAGIC = 0xcafed00dL;
     private static final int HANDLE_MAGIC_SIZE = 32;
+    private static final int USE_LOCAL_NAMESERVERS_FLAG = 0x80000000;
 
     // A boolean to control how getAllByName()/getByName() behaves in the face
     // of Private DNS.
@@ -189,7 +190,7 @@
      */
     public int getNetIdForResolv() {
         return mPrivateDnsBypass
-                ? (int) (0x80000000L | (long) netId)  // Non-portable DNS resolution flag.
+                ? (USE_LOCAL_NAMESERVERS_FLAG | netId)  // Non-portable DNS resolution flag.
                 : netId;
     }
 
@@ -452,12 +453,13 @@
             throw new IllegalArgumentException(
                     "Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network.");
         }
-        if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC
-                || networkHandle < 0) {
+        if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC) {
             throw new IllegalArgumentException(
                     "Value passed to fromNetworkHandle() is not a network handle.");
         }
-        return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE));
+        final int netIdForResolv = (int) (networkHandle >>> HANDLE_MAGIC_SIZE);
+        return new Network((netIdForResolv & ~USE_LOCAL_NAMESERVERS_FLAG),
+                ((netIdForResolv & USE_LOCAL_NAMESERVERS_FLAG) != 0) /* privateDnsBypass */);
     }
 
     /**
@@ -485,7 +487,7 @@
         if (netId == 0) {
             return 0L;  // make this zero condition obvious for debugging
         }
-        return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC;
+        return (((long) getNetIdForResolv()) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC;
     }
 
     // implement the Parcelable interface
diff --git a/framework/src/android/net/NetworkProvider.java b/framework/src/android/net/NetworkProvider.java
index 8f93047..cfb7325 100644
--- a/framework/src/android/net/NetworkProvider.java
+++ b/framework/src/android/net/NetworkProvider.java
@@ -168,12 +168,17 @@
     }
 
     /** @hide */
-    // TODO : make @SystemApi when the impl is complete
+    @SystemApi
     public interface NetworkOfferCallback {
-        /** Called by the system when this offer is needed to satisfy some networking request. */
-        void onOfferNeeded(@NonNull NetworkRequest request, int providerId);
-        /** Called by the system when this offer is no longer needed. */
-        void onOfferUnneeded(@NonNull NetworkRequest request);
+        /**
+         * Called by the system when a network for this offer is needed to satisfy some
+         * networking request.
+         */
+        void onNetworkNeeded(@NonNull NetworkRequest request);
+        /**
+         * Called by the system when this offer is no longer valuable for this request.
+         */
+        void onNetworkUnneeded(@NonNull NetworkRequest request);
     }
 
     private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub {
@@ -187,14 +192,13 @@
         }
 
         @Override
-        public void onOfferNeeded(final @NonNull NetworkRequest request,
-                final int providerId) {
-            mExecutor.execute(() -> callback.onOfferNeeded(request, providerId));
+        public void onNetworkNeeded(final @NonNull NetworkRequest request) {
+            mExecutor.execute(() -> callback.onNetworkNeeded(request));
         }
 
         @Override
-        public void onOfferUnneeded(final @NonNull NetworkRequest request) {
-            mExecutor.execute(() -> callback.onOfferUnneeded(request));
+        public void onNetworkUnneeded(final @NonNull NetworkRequest request) {
+            mExecutor.execute(() -> callback.onNetworkUnneeded(request));
         }
     }
 
@@ -213,48 +217,51 @@
     }
 
     /**
-     * Register or update an offer for network with the passed caps and score.
+     * Register or update an offer for network with the passed capabilities and score.
      *
-     * A NetworkProvider's job is to provide networks. This function is how a provider tells the
+     * A NetworkProvider's role is to provide networks. This method is how a provider tells the
      * connectivity stack what kind of network it may provide. The score and caps arguments act
-     * as filters that the connectivity stack uses to tell when the offer is necessary. When an
-     * offer might be advantageous over existing networks, the provider will receive a call to
-     * the associated callback's {@link NetworkOfferCallback#onOfferNeeded} method. The provider
-     * should then try to bring up this network. When an offer is no longer needed, the stack
-     * will inform the provider by calling {@link NetworkOfferCallback#onOfferUnneeded}. The
+     * as filters that the connectivity stack uses to tell when the offer is valuable. When an
+     * offer might be preferred over existing networks, the provider will receive a call to
+     * the associated callback's {@link NetworkOfferCallback#onNetworkNeeded} method. The provider
+     * should then try to bring up this network. When an offer is no longer useful, the stack
+     * will inform the provider by calling {@link NetworkOfferCallback#onNetworkUnneeded}. The
      * provider should stop trying to bring up such a network, or disconnect it if it already has
      * one.
      *
-     * The stack determines what offers are needed according to what networks are currently
+     * The stack determines what offers are valuable according to what networks are currently
      * available to the system, and what networking requests are made by applications. If an
-     * offer looks like it could be a better choice than any existing network for any particular
-     * request, that's when the stack decides the offer is needed. If the current networking
-     * requests are all satisfied by networks that this offer can't possibly be a better match
-     * for, that's when the offer is unneeded. An offer starts off as unneeded ; the provider
-     * should not try to bring up the network until {@link NetworkOfferCallback#onOfferNeeded}
-     * is called.
+     * offer looks like it could connect a better network than any existing network for any
+     * particular request, that's when the stack decides the network is needed. If the current
+     * networking requests are all satisfied by networks that this offer couldn't possibly be a
+     * better match for, that's when the offer is no longer valuable. An offer starts out as
+     * unneeded ; the provider should not try to bring up the network until
+     * {@link NetworkOfferCallback#onNetworkNeeded} is called.
      *
      * Note that the offers are non-binding to the providers, in particular because providers
      * often don't know if they will be able to bring up such a network at any given time. For
-     * example, no wireless network may be in range when the offer is needed. This is fine and
-     * expected ; the provider should simply continue to try to bring up the network and do so
+     * example, no wireless network may be in range when the offer would be valuable. This is fine
+     * and expected ; the provider should simply continue to try to bring up the network and do so
      * if/when it becomes possible. In the mean time, the stack will continue to satisfy requests
      * with the best network currently available, or if none, keep the apps informed that no
      * network can currently satisfy this request. When/if the provider can bring up the network,
      * the connectivity stack will match it against requests, and inform interested apps of the
      * availability of this network. This may, in turn, render the offer of some other provider
-     * unneeded if all requests it used to satisfy are now better served by this network.
+     * low-value if all requests it used to satisfy are now better served by this network.
      *
      * A network can become unneeded for a reason like the above : whether the provider managed
      * to bring up the offered network after it became needed or not, some other provider may
-     * bring up a better network than this one, making this offer unneeded. A network may also
+     * bring up a better network than this one, making this network unneeded. A network may also
      * become unneeded if the application making the request withdrew it (for example, after it
      * is done transferring data, or if the user canceled an operation).
      *
      * The capabilities and score act as filters as to what requests the provider will see.
      * They are not promises, but for best performance, the providers should strive to put
-     * as much known information as possible in the offer. For capabilities in particular, it
-     * should put all NetworkAgent-managed capabilities a network may have, even if it doesn't
+     * as much known information as possible in the offer. For the score, it should put as
+     * strong a score as the networks will have, since this will filter what requests the
+     * provider sees – it's not a promise, it only serves to avoid sending requests that
+     * the provider can't ever hope to satisfy better than any current network. For capabilities,
+     * it should put all NetworkAgent-managed capabilities a network may have, even if it doesn't
      * have them at first. This applies to INTERNET, for example ; if a provider thinks the
      * network it can bring up for this offer may offer Internet access it should include the
      * INTERNET bit. It's fine if the brought up network ends up not actually having INTERNET.
@@ -267,11 +274,14 @@
      *
      * @hide
      */
-    // TODO : make @SystemApi when the impl is complete
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
-    public void offerNetwork(@NonNull final NetworkScore score,
+    public void registerNetworkOffer(@NonNull final NetworkScore score,
             @NonNull final NetworkCapabilities caps, @NonNull final Executor executor,
             @NonNull final NetworkOfferCallback callback) {
+        // Can't offer a network with a provider that is not yet registered or already unregistered.
+        final int providerId = mProviderId;
+        if (providerId == ID_NONE) return;
         NetworkOfferCallbackProxy proxy = null;
         synchronized (mProxies) {
             for (final NetworkOfferCallbackProxy existingProxy : mProxies) {
@@ -285,7 +295,8 @@
                 mProxies.add(proxy);
             }
         }
-        mContext.getSystemService(ConnectivityManager.class).offerNetwork(this, score, caps, proxy);
+        mContext.getSystemService(ConnectivityManager.class)
+                .offerNetwork(providerId, score, caps, proxy);
     }
 
     /**
@@ -302,9 +313,9 @@
      *
      * @hide
      */
-    // TODO : make @SystemApi when the impl is complete
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
-    public void unofferNetwork(final @NonNull NetworkOfferCallback callback) {
+    public void unregisterNetworkOffer(final @NonNull NetworkOfferCallback callback) {
         final NetworkOfferCallbackProxy proxy = findProxyForCallback(callback);
         if (null == proxy) return;
         mProxies.remove(proxy);
diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java
index 6584993..0dee225 100644
--- a/framework/src/android/net/NetworkScore.java
+++ b/framework/src/android/net/NetworkScore.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
@@ -23,6 +24,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Object representing the quality of a network as perceived by the user.
  *
@@ -36,25 +40,57 @@
     // a migration.
     private final int mLegacyInt;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            KEEP_CONNECTED_NONE,
+            KEEP_CONNECTED_FOR_HANDOVER
+    })
+    public @interface KeepConnectedReason { }
+
+    public static final int KEEP_CONNECTED_NONE = 0;
+    public static final int KEEP_CONNECTED_FOR_HANDOVER = 1;
+
     // Agent-managed policies
-    // TODO : add them here, starting from 1
+    // This network should lose to a wifi that has ever been validated
+    // NOTE : temporarily this policy is managed by ConnectivityService, because of legacy. The
+    // legacy design has this bit global to the system and tacked on WiFi which means it will affect
+    // networks from carriers who don't want it and non-carrier networks, which is bad for users.
+    // The S design has this on mobile networks only, so this can be fixed eventually ; as CS
+    // doesn't know what carriers need this bit, the initial S implementation will continue to
+    // affect other carriers but will at least leave non-mobile networks alone. Eventually Telephony
+    // should set this on networks from carriers that require it.
     /** @hide */
-    public static final int MIN_AGENT_MANAGED_POLICY = 0;
+    public static final int POLICY_YIELD_TO_BAD_WIFI = 1;
+    // This network is primary for this transport.
     /** @hide */
-    public static final int MAX_AGENT_MANAGED_POLICY = -1;
+    public static final int POLICY_TRANSPORT_PRIMARY = 2;
+    // This network is exiting : it will likely disconnect in a few seconds.
+    /** @hide */
+    public static final int POLICY_EXITING = 3;
+
+    /** @hide */
+    public static final int MIN_AGENT_MANAGED_POLICY = POLICY_YIELD_TO_BAD_WIFI;
+    /** @hide */
+    public static final int MAX_AGENT_MANAGED_POLICY = POLICY_EXITING;
 
     // Bitmask of all the policies applied to this score.
     private final long mPolicies;
 
+    private final int mKeepConnectedReason;
+
     /** @hide */
-    NetworkScore(final int legacyInt, final long policies) {
+    NetworkScore(final int legacyInt, final long policies,
+            @KeepConnectedReason final int keepConnectedReason) {
         mLegacyInt = legacyInt;
         mPolicies = policies;
+        mKeepConnectedReason = keepConnectedReason;
     }
 
     private NetworkScore(@NonNull final Parcel in) {
         mLegacyInt = in.readInt();
         mPolicies = in.readLong();
+        mKeepConnectedReason = in.readInt();
     }
 
     public int getLegacyInt() {
@@ -62,6 +98,13 @@
     }
 
     /**
+     * Returns the keep-connected reason, or KEEP_CONNECTED_NONE.
+     */
+    public int getKeepConnectedReason() {
+        return mKeepConnectedReason;
+    }
+
+    /**
      * @return whether this score has a particular policy.
      *
      * @hide
@@ -71,15 +114,70 @@
         return 0 != (mPolicies & (1L << policy));
     }
 
+    /**
+     * To the exclusive usage of FullScore
+     * @hide
+     */
+    public long getPolicies() {
+        return mPolicies;
+    }
+
+    /**
+     * Whether this network should yield to a previously validated wifi gone bad.
+     *
+     * If this policy is set, other things being equal, the device will prefer a previously
+     * validated WiFi even if this network is validated and the WiFi is not.
+     * If this policy is not set, the device prefers the validated network.
+     *
+     * @hide
+     */
+    // TODO : Unhide this for telephony and have telephony call it on the relevant carriers.
+    // In the mean time this is handled by Connectivity in a backward-compatible manner.
+    public boolean shouldYieldToBadWifi() {
+        return hasPolicy(POLICY_YIELD_TO_BAD_WIFI);
+    }
+
+    /**
+     * Whether this network is primary for this transport.
+     *
+     * When multiple networks of the same transport are active, the device prefers the ones that
+     * are primary. This is meant in particular for DS-DA devices with a user setting to choose the
+     * default SIM card, or for WiFi STA+STA and make-before-break cases.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isTransportPrimary() {
+        return hasPolicy(POLICY_TRANSPORT_PRIMARY);
+    }
+
+    /**
+     * Whether this network is exiting.
+     *
+     * If this policy is set, the device will expect this network to disconnect within seconds.
+     * It will try to migrate to some other network if any is available, policy permitting, to
+     * avoid service disruption.
+     * This is useful in particular when a good cellular network is available and WiFi is getting
+     * weak and risks disconnecting soon. The WiFi network should be marked as exiting so that
+     * the device will prefer the reliable mobile network over this soon-to-be-lost WiFi.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isExiting() {
+        return hasPolicy(POLICY_EXITING);
+    }
+
     @Override
     public String toString() {
-        return "Score(" + mLegacyInt + ")";
+        return "Score(" + mLegacyInt + " ; Policies : " + mPolicies + ")";
     }
 
     @Override
     public void writeToParcel(@NonNull final Parcel dest, final int flags) {
         dest.writeInt(mLegacyInt);
         dest.writeLong(mPolicies);
+        dest.writeInt(mKeepConnectedReason);
     }
 
     @Override
@@ -108,6 +206,8 @@
         private static final long POLICY_NONE = 0L;
         private static final int INVALID_LEGACY_INT = Integer.MIN_VALUE;
         private int mLegacyInt = INVALID_LEGACY_INT;
+        private int mKeepConnectedReason = KEEP_CONNECTED_NONE;
+        private int mPolicies = 0;
 
         /**
          * Sets the legacy int for this score.
@@ -123,13 +223,93 @@
             return this;
         }
 
+
+        /**
+         * Set for a network that should never be preferred to a wifi that has ever been validated
+         *
+         * If this policy is set, other things being equal, the device will prefer a previously
+         * validated WiFi even if this network is validated and the WiFi is not.
+         * If this policy is not set, the device prefers the validated network.
+         *
+         * @return this builder
+         * @hide
+         */
+        // TODO : Unhide this for telephony and have telephony call it on the relevant carriers.
+        // In the mean time this is handled by Connectivity in a backward-compatible manner.
+        @NonNull
+        public Builder setShouldYieldToBadWifi(final boolean val) {
+            if (val) {
+                mPolicies |= (1L << POLICY_YIELD_TO_BAD_WIFI);
+            } else {
+                mPolicies &= ~(1L << POLICY_YIELD_TO_BAD_WIFI);
+            }
+            return this;
+        }
+
+        /**
+         * Set for a network that is primary for this transport.
+         *
+         * When multiple networks of the same transport are active, the device prefers the ones that
+         * are primary. This is meant in particular for DS-DA devices with a user setting to choose
+         * the default SIM card, or for WiFi STA+STA and make-before-break cases.
+         *
+         * @return this builder
+         * @hide
+         */
+        @SystemApi
+        @NonNull
+        public Builder setTransportPrimary(final boolean val) {
+            if (val) {
+                mPolicies |= (1L << POLICY_TRANSPORT_PRIMARY);
+            } else {
+                mPolicies &= ~(1L << POLICY_TRANSPORT_PRIMARY);
+            }
+            return this;
+        }
+
+        /**
+         * Set for a network that will likely disconnect in a few seconds.
+         *
+         * If this policy is set, the device will expect this network to disconnect within seconds.
+         * It will try to migrate to some other network if any is available, policy permitting, to
+         * avoid service disruption.
+         * This is useful in particular when a good cellular network is available and WiFi is
+         * getting weak and risks disconnecting soon. The WiFi network should be marked as exiting
+         * so that the device will prefer the reliable mobile network over this soon-to-be-lost
+         * WiFi.
+         *
+         * @return this builder
+         * @hide
+         */
+        @SystemApi
+        @NonNull
+        public Builder setExiting(final boolean val) {
+            if (val) {
+                mPolicies |= (1L << POLICY_EXITING);
+            } else {
+                mPolicies &= ~(1L << POLICY_EXITING);
+            }
+            return this;
+        }
+
+        /**
+         * Set the keep-connected reason.
+         *
+         * This can be reset by calling it again with {@link KEEP_CONNECTED_NONE}.
+         */
+        @NonNull
+        public Builder setKeepConnectedReason(@KeepConnectedReason final int reason) {
+            mKeepConnectedReason = reason;
+            return this;
+        }
+
         /**
          * Builds this NetworkScore.
          * @return The built NetworkScore object.
          */
         @NonNull
         public NetworkScore build() {
-            return new NetworkScore(mLegacyInt, POLICY_NONE);
+            return new NetworkScore(mLegacyInt, mPolicies, mKeepConnectedReason);
         }
     }
 }
diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java
index a92fda1..16a49bc 100644
--- a/framework/src/android/net/NetworkUtils.java
+++ b/framework/src/android/net/NetworkUtils.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.net.ConnectivityManager.NETID_UNSET;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.system.ErrnoException;
@@ -55,6 +57,8 @@
      */
     public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
 
+    private static native boolean bindProcessToNetworkHandle(long netHandle);
+
     /**
      * Binds the current process to the network designated by {@code netId}.  All sockets created
      * in the future (and not explicitly bound via a bound {@link SocketFactory} (see
@@ -63,13 +67,20 @@
      * is by design so an application doesn't accidentally use sockets it thinks are still bound to
      * a particular {@code Network}.  Passing NETID_UNSET clears the binding.
      */
-    public native static boolean bindProcessToNetwork(int netId);
+    public static boolean bindProcessToNetwork(int netId) {
+        return bindProcessToNetworkHandle(new Network(netId).getNetworkHandle());
+    }
+
+    private static native long getBoundNetworkHandleForProcess();
 
     /**
      * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
      * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
      */
-    public native static int getBoundNetworkForProcess();
+    public static int getBoundNetworkForProcess() {
+        final long netHandle = getBoundNetworkHandleForProcess();
+        return netHandle == 0L ? NETID_UNSET : Network.fromNetworkHandle(netHandle).getNetId();
+    }
 
     /**
      * Binds host resolutions performed by this process to the network designated by {@code netId}.
@@ -81,12 +92,16 @@
     @Deprecated
     public native static boolean bindProcessToNetworkForHostResolution(int netId);
 
+    private static native int bindSocketToNetworkHandle(FileDescriptor fd, long netHandle);
+
     /**
      * Explicitly binds {@code fd} to the network designated by {@code netId}.  This
      * overrides any binding via {@link #bindProcessToNetwork}.
      * @return 0 on success or negative errno on failure.
      */
-    public static native int bindSocketToNetwork(FileDescriptor fd, int netId);
+    public static int bindSocketToNetwork(FileDescriptor fd, int netId) {
+        return bindSocketToNetworkHandle(fd, new Network(netId).getNetworkHandle());
+    }
 
     /**
      * Determine if {@code uid} can access network designated by {@code netId}.
@@ -97,14 +112,22 @@
         return false;
     }
 
+    private static native FileDescriptor resNetworkSend(
+            long netHandle, byte[] msg, int msglen, int flags) throws ErrnoException;
+
     /**
      * DNS resolver series jni method.
      * Issue the query {@code msg} on the network designated by {@code netId}.
      * {@code flags} is an additional config to control actual querying behavior.
      * @return a file descriptor to watch for read events
      */
-    public static native FileDescriptor resNetworkSend(
-            int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
+    public static FileDescriptor resNetworkSend(
+            int netId, byte[] msg, int msglen, int flags) throws ErrnoException {
+        return resNetworkSend(new Network(netId).getNetworkHandle(), msg, msglen, flags);
+    }
+
+    private static native FileDescriptor resNetworkQuery(
+            long netHandle, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
 
     /**
      * DNS resolver series jni method.
@@ -113,8 +136,11 @@
      * {@code flags} is an additional config to control actual querying behavior.
      * @return a file descriptor to watch for read events
      */
-    public static native FileDescriptor resNetworkQuery(
-            int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
+    public static FileDescriptor resNetworkQuery(
+            int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException {
+        return resNetworkQuery(new Network(netId).getNetworkHandle(), dname, nsClass, nsType,
+                flags);
+    }
 
     /**
      * DNS resolver series jni method.
diff --git a/service/Android.bp b/service/Android.bp
index b6adf79..ec8887c 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -32,6 +32,7 @@
         "jni/com_android_server_TestNetworkService.cpp",
         "jni/onload.cpp",
     ],
+    stl: "libc++_static",
     shared_libs: [
         "libbase",
         "liblog",
@@ -47,22 +48,33 @@
 
 java_library {
     name: "service-connectivity-pre-jarjar",
+    sdk_version: "system_server_current",
     srcs: [
         "src/**/*.java",
         ":framework-connectivity-shared-srcs",
+        ":services-connectivity-shared-srcs",
+        // TODO: move to net-utils-device-common, enable shrink optimization to avoid extra classes
+        ":net-module-utils-srcs",
     ],
     libs: [
-        "android.net.ipsec.ike",
-        "services.core",
-        "services.net",
+        // TODO (b/183097033) remove once system_server_current includes core_current
+        "stable.core.platform.api.stubs",
+        "android_system_server_stubs_current",
+        "framework-annotations-lib",
+        "framework-connectivity.impl",
+        "framework-tethering.stubs.module_lib",
+        "framework-wifi.stubs.module_lib",
         "unsupportedappusage",
         "ServiceConnectivityResources",
     ],
     static_libs: [
+        "dnsresolver_aidl_interface-V7-java",
         "modules-utils-os",
         "net-utils-device-common",
         "net-utils-framework-common",
         "netd-client",
+        "netlink-client",
+        "networkstack-client",
         "PlatformProperties",
         "service-connectivity-protos",
     ],
@@ -74,6 +86,7 @@
 
 java_library {
     name: "service-connectivity-protos",
+    sdk_version: "system_current",
     proto: {
         type: "nano",
     },
@@ -89,6 +102,7 @@
 
 java_library {
     name: "service-connectivity",
+    sdk_version: "system_server_current",
     installable: true,
     static_libs: [
         "service-connectivity-pre-jarjar",
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index d54923c..f57761f 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -88,7 +88,6 @@
 import static java.util.Map.Entry;
 
 import android.Manifest;
-import android.annotation.BoolRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
@@ -151,7 +150,6 @@
 import android.net.NetworkScore;
 import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
-import android.net.NetworkStackClient;
 import android.net.NetworkState;
 import android.net.NetworkStateSnapshot;
 import android.net.NetworkTestResultParcelable;
@@ -178,13 +176,14 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
+import android.net.networkstack.ModuleNetworkStackClient;
+import android.net.networkstack.NetworkStackClientBase;
 import android.net.resolv.aidl.DnsHealthEventParcel;
 import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
 import android.net.resolv.aidl.Nat64PrefixEventParcel;
 import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
 import android.os.BatteryStatsManager;
 import android.os.Binder;
 import android.os.Build;
@@ -619,6 +618,11 @@
     private static final int EVENT_UNREGISTER_NETWORK_OFFER = 53;
 
     /**
+     * Used internally when MOBILE_DATA_PREFERRED_UIDS setting changed.
+     */
+    private static final int EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED = 54;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -1185,10 +1189,10 @@
         }
 
         /**
-         * Get a reference to the NetworkStackClient.
+         * Get a reference to the ModuleNetworkStackClient.
          */
-        public NetworkStackClient getNetworkStack() {
-            return NetworkStackClient.getInstance();
+        public NetworkStackClientBase getNetworkStack() {
+            return ModuleNetworkStackClient.getInstance(null);
         }
 
         /**
@@ -1247,7 +1251,8 @@
 
     public ConnectivityService(Context context) {
         this(context, getDnsResolver(context), new IpConnectivityLog(),
-                NetdService.getInstance(), new Dependencies());
+                INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+                new Dependencies());
     }
 
     @VisibleForTesting
@@ -1379,7 +1384,7 @@
         mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
 
         mMultinetworkPolicyTracker = mDeps.makeMultinetworkPolicyTracker(
-                mContext, mHandler, () -> rematchForAvoidBadWifiUpdate());
+                mContext, mHandler, () -> updateAvoidBadWifi());
         mMultinetworkPolicyTracker.start();
 
         mDnsManager = new DnsManager(mContext, mDnsResolver);
@@ -1461,7 +1466,12 @@
         mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
     }
 
-    private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, @BoolRes int id) {
+    @VisibleForTesting
+    void updateMobileDataPreferredUids() {
+        mHandler.sendEmptyMessage(EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED);
+    }
+
+    private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, int id) {
         final boolean enable = mContext.getResources().getBoolean(id);
         handleAlwaysOnNetworkRequest(networkRequest, enable);
     }
@@ -1505,6 +1515,8 @@
                 vehicleAlwaysRequested || legacyAlwaysRequested);
     }
 
+    // Note that registering observer for setting do not get initial callback when registering,
+    // callers might have self-initialization to update status if need.
     private void registerSettingsCallbacks() {
         // Watch for global HTTP proxy changes.
         mSettingsObserver.observe(
@@ -1520,6 +1532,11 @@
         mSettingsObserver.observe(
                 Settings.Global.getUriFor(ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED),
                 EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
+
+        // Watch for mobile data preferred uids changes.
+        mSettingsObserver.observe(
+                Settings.Secure.getUriFor(ConnectivitySettingsManager.MOBILE_DATA_PREFERRED_UIDS),
+                EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED);
     }
 
     private void registerPrivateDnsSettingsCallbacks() {
@@ -2718,6 +2735,13 @@
 
         // Create network requests for always-on networks.
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS));
+
+        // Update mobile data preference if necessary.
+        // Note that empty uid list can be skip here only because no uid rules applied before system
+        // ready. Normally, the empty uid list means to clear the uids rules on netd.
+        if (!ConnectivitySettingsManager.getMobileDataPreferredUids(mContext).isEmpty()) {
+            updateMobileDataPreferredUids();
+        }
     }
 
     /**
@@ -2990,10 +3014,6 @@
         }
 
         pw.println();
-        pw.println("NetworkStackClient logs:");
-        pw.increaseIndent();
-        NetworkStackClient.getInstance().dump(pw);
-        pw.decreaseIndent();
 
         pw.println();
         pw.println("Permission Monitor:");
@@ -3340,8 +3360,6 @@
                 nai.lastValidated = valid;
                 nai.everValidated |= valid;
                 updateCapabilities(oldScore, nai, nai.networkCapabilities);
-                // If score has changed, rebroadcast to NetworkProviders. b/17726566
-                if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                 if (valid) {
                     handleFreshlyValidatedNetwork(nai);
                     // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and
@@ -3762,9 +3780,12 @@
             if (currentNetwork != null
                     && currentNetwork.network.getNetId() == nai.network.getNetId()) {
                 // uid rules for this network will be removed in destroyNativeNetwork(nai).
+                // TODO : setting the satisfier is in fact the job of the rematch. Teach the
+                // rematch not to keep disconnected agents instead of setting it here ; this
+                // will also allow removing updating the offers below.
                 nri.setSatisfier(null, null);
-                if (request.isRequest()) {
-                    sendUpdatedScoreToFactories(request, null);
+                for (final NetworkOfferInfo noi : mNetworkOffers) {
+                    informOffer(nri, noi.offer, mNetworkRanker);
                 }
 
                 if (mDefaultRequest == nri) {
@@ -3920,16 +3941,13 @@
         }
 
         rematchAllNetworksAndRequests();
-        for (final NetworkRequestInfo nri : nris) {
-            // If the nri is satisfied, return as its score has already been sent if needed.
-            if (nri.isBeingSatisfied()) {
-                return;
-            }
 
-            // As this request was not satisfied on rematch and thus never had any scores sent to
-            // the factories, send null now for each request of type REQUEST.
-            for (final NetworkRequest req : nri.mRequests) {
-                if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
+        // Requests that have not been matched to a network will not have been sent to the
+        // providers, because the old satisfier and the new satisfier are the same (null in this
+        // case). Send these requests to the providers.
+        for (final NetworkRequestInfo nri : nris) {
+            for (final NetworkOfferInfo noi : mNetworkOffers) {
+                informOffer(nri, noi.offer, mNetworkRanker);
             }
         }
     }
@@ -3957,6 +3975,12 @@
     //   then it should be lingered.
     private boolean unneeded(NetworkAgentInfo nai, UnneededFor reason) {
         ensureRunningOnConnectivityServiceThread();
+
+        if (!nai.everConnected || nai.isVPN() || nai.isInactive()
+                || nai.getScore().getKeepConnectedReason() != NetworkScore.KEEP_CONNECTED_NONE) {
+            return false;
+        }
+
         final int numRequests;
         switch (reason) {
             case TEARDOWN:
@@ -3970,9 +3994,8 @@
                 return true;
         }
 
-        if (!nai.everConnected || nai.isVPN() || nai.isInactive() || numRequests > 0) {
-            return false;
-        }
+        if (numRequests > 0) return false;
+
         for (NetworkRequestInfo nri : mNetworkRequests.values()) {
             if (reason == UnneededFor.LINGER
                     && !nri.isMultilayerRequest()
@@ -4137,7 +4160,15 @@
             }
         }
 
-        cancelNpiRequests(nri);
+        // For all outstanding offers, cancel any of the layers of this NRI that used to be
+        // needed for this offer.
+        for (final NetworkOfferInfo noi : mNetworkOffers) {
+            for (final NetworkRequest req : nri.mRequests) {
+                if (req.isRequest() && noi.offer.neededFor(req)) {
+                    noi.offer.onNetworkUnneeded(req);
+                }
+            }
+        }
     }
 
     private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
@@ -4150,20 +4181,6 @@
         }
     }
 
-    private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) {
-        for (final NetworkRequest req : nri.mRequests) {
-            cancelNpiRequest(req);
-        }
-    }
-
-    private void cancelNpiRequest(@NonNull final NetworkRequest req) {
-        if (req.isRequest()) {
-            for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
-                npi.cancelRequest(req);
-            }
-        }
-    }
-
     private void removeListenRequestFromNetworks(@NonNull final NetworkRequest req) {
         // listens don't have a singular affected Network. Check all networks to see
         // if this listen request applies and remove it.
@@ -4290,7 +4307,6 @@
             nai.networkAgentConfig.acceptPartialConnectivity = accept;
             nai.updateScoreForNetworkAgentConfigUpdate();
             rematchAllNetworksAndRequests();
-            sendUpdatedScoreToFactories(nai);
         }
 
         if (always) {
@@ -4358,7 +4374,6 @@
         if (!nai.avoidUnvalidated) {
             nai.avoidUnvalidated = true;
             rematchAllNetworksAndRequests();
-            sendUpdatedScoreToFactories(nai);
         }
     }
 
@@ -4463,14 +4478,11 @@
         return avoidBadWifi();
     }
 
-
-    private void rematchForAvoidBadWifiUpdate() {
-        rematchAllNetworksAndRequests();
-        for (NetworkAgentInfo nai: mNetworkAgentInfos) {
-            if (nai.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
-                sendUpdatedScoreToFactories(nai);
-            }
+    private void updateAvoidBadWifi() {
+        for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+            nai.updateScoreForNetworkAgentConfigUpdate();
         }
+        rematchAllNetworksAndRequests();
     }
 
     // TODO: Evaluate whether this is of interest to other consumers of
@@ -4806,6 +4818,9 @@
                 case EVENT_REPORT_NETWORK_ACTIVITY:
                     mNetworkActivityTracker.handleReportNetworkActivity();
                     break;
+                case EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED:
+                    handleMobileDataPreferredUidsChanged();
+                    break;
             }
         }
     }
@@ -5492,24 +5507,6 @@
             }
         }
 
-        void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) {
-            try {
-                messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj));
-            } catch (RemoteException e) {
-                // Remote process died. Ignore; the death recipient will remove this
-                // NetworkProviderInfo from mNetworkProviderInfos.
-            }
-        }
-
-        void requestNetwork(NetworkRequest request, int score, int servingProviderId) {
-            sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score,
-                            servingProviderId, request);
-        }
-
-        void cancelRequest(NetworkRequest request) {
-            sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request);
-        }
-
         void connect(Context context, Handler handler) {
             try {
                 messenger.getBinder().linkToDeath(mDeathRecipient, 0);
@@ -5610,7 +5607,7 @@
          * Get the list of UIDs this nri applies to.
          */
         @NonNull
-        private Set<UidRange> getUids() {
+        Set<UidRange> getUids() {
             // networkCapabilities.getUids() returns a defensive copy.
             // multilayer requests will all have the same uids so return the first one.
             final Set<UidRange> uids = mRequests.get(0).networkCapabilities.getUidRanges();
@@ -6218,7 +6215,6 @@
         if (DBG) log("Got NetworkProvider Messenger for " + npi.name);
         mNetworkProviderInfos.put(npi.messenger, npi);
         npi.connect(mContext, mTrackerHandler);
-        sendAllRequestsToProvider(npi);
     }
 
     @Override
@@ -6238,11 +6234,14 @@
     }
 
     @Override
-    public void offerNetwork(@NonNull final Messenger providerMessenger,
+    public void offerNetwork(final int providerId,
             @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps,
             @NonNull final INetworkOfferCallback callback) {
+        Objects.requireNonNull(score);
+        Objects.requireNonNull(caps);
+        Objects.requireNonNull(callback);
         final NetworkOffer offer = new NetworkOffer(
-                FullScore.makeProspectiveScore(score, caps), caps, callback, providerMessenger);
+                FullScore.makeProspectiveScore(score, caps), caps, callback, providerId);
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_OFFER, offer));
     }
 
@@ -6260,12 +6259,12 @@
         // Unregister all the offers from this provider
         final ArrayList<NetworkOfferInfo> toRemove = new ArrayList<>();
         for (final NetworkOfferInfo noi : mNetworkOffers) {
-            if (noi.offer.provider == messenger) {
+            if (noi.offer.providerId == npi.providerId) {
                 // Can't call handleUnregisterNetworkOffer here because iteration is in progress
                 toRemove.add(noi);
             }
         }
-        for (NetworkOfferInfo noi : toRemove) {
+        for (final NetworkOfferInfo noi : toRemove) {
             handleUnregisterNetworkOffer(noi);
         }
         if (DBG) log("unregisterNetworkProvider for " + npi.name);
@@ -6324,6 +6323,11 @@
     @NonNull
     private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences();
 
+    // A set of UIDs that should use mobile data preferentially if available. This object follows
+    // the same threading rules as the OEM network preferences above.
+    @NonNull
+    private Set<Integer> mMobileDataPreferredUids = new ArraySet<>();
+
     // OemNetworkPreferences activity String log entries.
     private static final int MAX_OEM_NETWORK_PREFERENCE_LOGS = 20;
     @NonNull
@@ -6645,6 +6649,13 @@
         }
     }
 
+    private boolean isNetworkProviderWithIdRegistered(final int providerId) {
+        for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+            if (npi.providerId == providerId) return true;
+        }
+        return false;
+    }
+
     /**
      * Register or update a network offer.
      * @param newOffer The new offer. If the callback member is the same as an existing
@@ -6652,7 +6663,7 @@
      */
     private void handleRegisterNetworkOffer(@NonNull final NetworkOffer newOffer) {
         ensureRunningOnConnectivityServiceThread();
-        if (null == mNetworkProviderInfos.get(newOffer.provider)) {
+        if (!isNetworkProviderWithIdRegistered(newOffer.providerId)) {
             // This may actually happen if a provider updates its score or registers and then
             // immediately unregisters. The offer would still be in the handler queue, but the
             // provider would have been removed.
@@ -6667,19 +6678,19 @@
         }
         final NetworkOfferInfo noi = new NetworkOfferInfo(newOffer);
         try {
-            noi.offer.provider.getBinder().linkToDeath(noi, 0 /* flags */);
+            noi.offer.callback.asBinder().linkToDeath(noi, 0 /* flags */);
         } catch (RemoteException e) {
             noi.binderDied();
             return;
         }
         mNetworkOffers.add(noi);
-        // TODO : send requests to the provider.
+        issueNetworkNeeds(noi);
     }
 
     private void handleUnregisterNetworkOffer(@NonNull final NetworkOfferInfo noi) {
         ensureRunningOnConnectivityServiceThread();
         mNetworkOffers.remove(noi);
-        noi.offer.provider.getBinder().unlinkToDeath(noi, 0 /* flags */);
+        noi.offer.callback.asBinder().unlinkToDeath(noi, 0 /* flags */);
     }
 
     @Nullable private NetworkOfferInfo findNetworkOfferInfoByCallback(
@@ -7448,100 +7459,6 @@
         updateLinkProperties(nai, newLp, new LinkProperties(nai.linkProperties));
     }
 
-    private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) {
-        for (int i = 0; i < nai.numNetworkRequests(); i++) {
-            NetworkRequest nr = nai.requestAt(i);
-            // Don't send listening or track default request to factories. b/17393458
-            if (!nr.isRequest()) continue;
-            sendUpdatedScoreToFactories(nr, nai);
-        }
-    }
-
-    private void sendUpdatedScoreToFactories(
-            @NonNull final NetworkReassignment.RequestReassignment event) {
-        // If a request of type REQUEST is now being satisfied by a new network.
-        if (null != event.mNewNetworkRequest && event.mNewNetworkRequest.isRequest()) {
-            sendUpdatedScoreToFactories(event.mNewNetworkRequest, event.mNewNetwork);
-        }
-
-        // If a previously satisfied request of type REQUEST is no longer being satisfied.
-        if (null != event.mOldNetworkRequest && event.mOldNetworkRequest.isRequest()
-                && event.mOldNetworkRequest != event.mNewNetworkRequest) {
-            sendUpdatedScoreToFactories(event.mOldNetworkRequest, null);
-        }
-
-        cancelMultilayerLowerPriorityNpiRequests(event.mNetworkRequestInfo);
-    }
-
-    /**
-     *  Cancel with all NPIs the given NRI's multilayer requests that are a lower priority than
-     *  its currently satisfied active request.
-     * @param nri the NRI to cancel lower priority requests for.
-     */
-    private void cancelMultilayerLowerPriorityNpiRequests(
-            @NonNull final NetworkRequestInfo nri) {
-        if (!nri.isMultilayerRequest() || null == nri.mActiveRequest) {
-            return;
-        }
-
-        final int indexOfNewRequest = nri.mRequests.indexOf(nri.mActiveRequest);
-        for (int i = indexOfNewRequest + 1; i < nri.mRequests.size(); i++) {
-            cancelNpiRequest(nri.mRequests.get(i));
-        }
-    }
-
-    private void sendUpdatedScoreToFactories(@NonNull NetworkRequest networkRequest,
-            @Nullable NetworkAgentInfo nai) {
-        final int score;
-        final int serial;
-        if (nai != null) {
-            score = nai.getCurrentScore();
-            serial = nai.factorySerialNumber;
-        } else {
-            score = 0;
-            serial = 0;
-        }
-        if (VDBG || DDBG){
-            log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
-        }
-        for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
-            npi.requestNetwork(networkRequest, score, serial);
-        }
-    }
-
-    /** Sends all current NetworkRequests to the specified factory. */
-    private void sendAllRequestsToProvider(@NonNull final NetworkProviderInfo npi) {
-        ensureRunningOnConnectivityServiceThread();
-        for (final NetworkRequestInfo nri : getNrisFromGlobalRequests()) {
-            for (final NetworkRequest req : nri.mRequests) {
-                if (!req.isRequest() && nri.getActiveRequest() == req) {
-                    break;
-                }
-                if (!req.isRequest()) {
-                    continue;
-                }
-                // Only set the nai for the request it is satisfying.
-                final NetworkAgentInfo nai =
-                        nri.getActiveRequest() == req ? nri.getSatisfier() : null;
-                final int score;
-                final int serial;
-                if (null != nai) {
-                    score = nai.getCurrentScore();
-                    serial = nai.factorySerialNumber;
-                } else {
-                    score = 0;
-                    serial = NetworkProvider.ID_NONE;
-                }
-                npi.requestNetwork(req, score, serial);
-                // For multilayer requests, don't send lower priority requests if a higher priority
-                // request is already satisfied.
-                if (null != nai) {
-                    break;
-                }
-            }
-        }
-    }
-
     private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent,
             int notificationType) {
         if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
@@ -7985,7 +7902,7 @@
             NetworkAgentInfo bestNetwork = null;
             NetworkRequest bestRequest = null;
             for (final NetworkRequest req : nri.mRequests) {
-                bestNetwork = mNetworkRanker.getBestNetwork(req, nais);
+                bestNetwork = mNetworkRanker.getBestNetwork(req, nais, nri.getSatisfier());
                 // Stop evaluating as the highest possible priority request is satisfied.
                 if (null != bestNetwork) {
                     bestRequest = req;
@@ -8033,6 +7950,7 @@
             log(changes.toString()); // Shorter form, only one line of log
         }
         applyNetworkReassignment(changes, now);
+        issueNetworkNeeds();
     }
 
     private void applyNetworkReassignment(@NonNull final NetworkReassignment changes,
@@ -8064,12 +7982,6 @@
         // before LegacyTypeTracker sends legacy broadcasts
         for (final NetworkReassignment.RequestReassignment event :
                 changes.getRequestReassignments()) {
-            // Tell NetworkProviders about the new score, so they can stop
-            // trying to connect if they know they cannot match it.
-            // TODO - this could get expensive if there are a lot of outstanding requests for this
-            // network. Think of a way to reduce this. Push netid->request mapping to each factory?
-            sendUpdatedScoreToFactories(event);
-
             if (null != event.mNewNetwork) {
                 notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo);
             } else {
@@ -8206,6 +8118,106 @@
         }
     }
 
+    private void issueNetworkNeeds() {
+        ensureRunningOnConnectivityServiceThread();
+        for (final NetworkOfferInfo noi : mNetworkOffers) {
+            issueNetworkNeeds(noi);
+        }
+    }
+
+    private void issueNetworkNeeds(@NonNull final NetworkOfferInfo noi) {
+        ensureRunningOnConnectivityServiceThread();
+        for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+            informOffer(nri, noi.offer, mNetworkRanker);
+        }
+    }
+
+    /**
+     * Inform a NetworkOffer about any new situation of a request.
+     *
+     * This function handles updates to offers. A number of events may happen that require
+     * updating the registrant for this offer about the situation :
+     * • The offer itself was updated. This may lead the offer to no longer being able
+     *     to satisfy a request or beat a satisfier (and therefore be no longer needed),
+     *     or conversely being strengthened enough to beat the satisfier (and therefore
+     *     start being needed)
+     * • The network satisfying a request changed (including cases where the request
+     *     starts or stops being satisfied). The new network may be a stronger or weaker
+     *     match than the old one, possibly affecting whether the offer is needed.
+     * • The network satisfying a request updated their score. This may lead the offer
+     *     to no longer be able to beat it if the current satisfier got better, or
+     *     conversely start being a good choice if the current satisfier got weaker.
+     *
+     * @param nri The request
+     * @param offer The offer. This may be an updated offer.
+     */
+    private static void informOffer(@NonNull NetworkRequestInfo nri,
+            @NonNull final NetworkOffer offer, @NonNull final NetworkRanker networkRanker) {
+        final NetworkRequest activeRequest = nri.isBeingSatisfied() ? nri.getActiveRequest() : null;
+        final NetworkAgentInfo satisfier = null != activeRequest ? nri.getSatisfier() : null;
+
+        // Multi-layer requests have a currently active request, the one being satisfied.
+        // Since the system will try to bring up a better network than is currently satisfying
+        // the request, NetworkProviders need to be told the offers matching the requests *above*
+        // the currently satisfied one are needed, that the ones *below* the satisfied one are
+        // not needed, and the offer is needed for the active request iff the offer can beat
+        // the satisfier.
+        // For non-multilayer requests, the logic above gracefully degenerates to only the
+        // last case.
+        // To achieve this, the loop below will proceed in three steps. In a first phase, inform
+        // providers that the offer is needed for this request, until the active request is found.
+        // In a second phase, deal with the currently active request. In a third phase, inform
+        // the providers that offer is unneeded for the remaining requests.
+
+        // First phase : inform providers of all requests above the active request.
+        int i;
+        for (i = 0; nri.mRequests.size() > i; ++i) {
+            final NetworkRequest request = nri.mRequests.get(i);
+            if (activeRequest == request) break; // Found the active request : go to phase 2
+            if (!request.isRequest()) continue; // Listens/track defaults are never sent to offers
+            // Since this request is higher-priority than the one currently satisfied, if the
+            // offer can satisfy it, the provider should try and bring up the network for sure ;
+            // no need to even ask the ranker – an offer that can satisfy is always better than
+            // no network. Hence tell the provider so unless it already knew.
+            if (request.canBeSatisfiedBy(offer.caps) && !offer.neededFor(request)) {
+                offer.onNetworkNeeded(request);
+            }
+        }
+
+        // Second phase : deal with the active request (if any)
+        if (null != activeRequest && activeRequest.isRequest()) {
+            final boolean oldNeeded = offer.neededFor(activeRequest);
+            // An offer is needed if it is currently served by this provider or if this offer
+            // can beat the current satisfier.
+            final boolean currentlyServing = satisfier != null
+                    && satisfier.factorySerialNumber == offer.providerId;
+            final boolean newNeeded = (currentlyServing
+                    || (activeRequest.canBeSatisfiedBy(offer.caps)
+                            && networkRanker.mightBeat(activeRequest, satisfier, offer)));
+            if (newNeeded != oldNeeded) {
+                if (newNeeded) {
+                    offer.onNetworkNeeded(activeRequest);
+                } else {
+                    // The offer used to be able to beat the satisfier. Now it can't.
+                    offer.onNetworkUnneeded(activeRequest);
+                }
+            }
+        }
+
+        // Third phase : inform the providers that the offer isn't needed for any request
+        // below the active one.
+        for (++i /* skip the active request */; nri.mRequests.size() > i; ++i) {
+            final NetworkRequest request = nri.mRequests.get(i);
+            if (!request.isRequest()) continue; // Listens/track defaults are never sent to offers
+            // Since this request is lower-priority than the one currently satisfied, if the
+            // offer can satisfy it, the provider should not try and bring up the network.
+            // Hence tell the provider so unless it already knew.
+            if (offer.neededFor(request)) {
+                offer.onNetworkUnneeded(request);
+            }
+        }
+    }
+
     private void addNetworkToLegacyTypeTracker(@NonNull final NetworkAgentInfo nai) {
         for (int i = 0; i < nai.numNetworkRequests(); i++) {
             NetworkRequest nr = nai.requestAt(i);
@@ -8371,7 +8383,6 @@
         if (VDBG || DDBG) log("updateNetworkScore for " + nai.toShortString() + " to " + score);
         nai.setScore(score);
         rematchAllNetworksAndRequests();
-        sendUpdatedScoreToFactories(nai);
     }
 
     // Notify only this one new request of the current state. Transfer all the
@@ -8778,7 +8789,7 @@
         }
     }
 
-    private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
+    private int getVpnType(@Nullable NetworkAgentInfo vpn) {
         if (vpn == null) return VpnManager.TYPE_VPN_NONE;
         final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
         if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE;
@@ -9691,7 +9702,8 @@
         // safe - it's just possible the value is slightly outdated. For the final check,
         // see #handleSetProfileNetworkPreference. But if this can be caught here it is a
         // lot easier to understand, so opportunistically check it.
-        if (!mOemNetworkPreferences.isEmpty()) {
+        // TODO: Have a priority for each preference.
+        if (!mOemNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) {
             throwConcurrentPreferenceException();
         }
         final NetworkCapabilities nc;
@@ -9750,7 +9762,8 @@
         // The binder call has already checked this, but as mOemNetworkPreferences is only
         // touched on the handler thread, it's theoretically not impossible that it has changed
         // since.
-        if (!mOemNetworkPreferences.isEmpty()) {
+        // TODO: Have a priority for each preference.
+        if (!mOemNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) {
             // This may happen on a device with an OEM preference set when a user is removed.
             // In this case, it's safe to ignore. In particular this happens in the tests.
             loge("handleSetProfileNetworkPreference, but OEM network preferences not empty");
@@ -9779,6 +9792,56 @@
         }
     }
 
+    @VisibleForTesting
+    @NonNull
+    ArraySet<NetworkRequestInfo> createNrisFromMobileDataPreferredUids(
+            @NonNull final Set<Integer> uids) {
+        final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
+        if (uids.size() == 0) {
+            // Should not create NetworkRequestInfo if no preferences. Without uid range in
+            // NetworkRequestInfo, makeDefaultForApps() would treat it as a illegal NRI.
+            if (DBG) log("Don't create NetworkRequestInfo because no preferences");
+            return nris;
+        }
+
+        final List<NetworkRequest> requests = new ArrayList<>();
+        // The NRI should be comprised of two layers:
+        // - The request for the mobile network preferred.
+        // - The request for the default network, for fallback.
+        requests.add(createDefaultInternetRequestForTransport(
+                TRANSPORT_CELLULAR, NetworkRequest.Type.LISTEN));
+        requests.add(createDefaultInternetRequestForTransport(
+                TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
+        final Set<UidRange> ranges = new ArraySet<>();
+        for (final int uid : uids) {
+            ranges.add(new UidRange(uid, uid));
+        }
+        setNetworkRequestUids(requests, ranges);
+        nris.add(new NetworkRequestInfo(Process.myUid(), requests));
+        return nris;
+    }
+
+    private void handleMobileDataPreferredUidsChanged() {
+        // Ignore update preference because it's not clear what preference should win in case both
+        // apply to the same app.
+        // TODO: Have a priority for each preference.
+        if (!mOemNetworkPreferences.isEmpty() || !mProfileNetworkPreferences.isEmpty()) {
+            loge("Ignore mobile data preference change because other preferences are not empty");
+            return;
+        }
+
+        mMobileDataPreferredUids = ConnectivitySettingsManager.getMobileDataPreferredUids(mContext);
+        mSystemNetworkRequestCounter.transact(
+                mDeps.getCallingUid(), 1 /* numOfNewRequests */,
+                () -> {
+                    final ArraySet<NetworkRequestInfo> nris =
+                            createNrisFromMobileDataPreferredUids(mMobileDataPreferredUids);
+                    replaceDefaultNetworkRequestsForPreference(nris);
+                });
+        // Finally, rematch.
+        rematchAllNetworksAndRequests();
+    }
+
     private void enforceAutomotiveDevice() {
         final boolean isAutomotiveDevice =
                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
@@ -9808,7 +9871,8 @@
         enforceAutomotiveDevice();
         enforceOemNetworkPreferencesPermission();
 
-        if (!mProfileNetworkPreferences.isEmpty()) {
+        // TODO: Have a priority for each preference.
+        if (!mProfileNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) {
             // Strictly speaking, mProfileNetworkPreferences should only be touched on the
             // handler thread. However it is an immutable object, so reading the reference is
             // safe - it's just possible the value is slightly outdated. For the final check,
@@ -9846,7 +9910,8 @@
         // The binder call has already checked this, but as mOemNetworkPreferences is only
         // touched on the handler thread, it's theoretically not impossible that it has changed
         // since.
-        if (!mProfileNetworkPreferences.isEmpty()) {
+        // TODO: Have a priority for each preference.
+        if (!mProfileNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) {
             logwtf("handleSetOemPreference, but per-profile network preferences not empty");
             return;
         }
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index f566277..09873f4 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -35,7 +35,6 @@
 import android.net.RouteInfo;
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkSpecifier;
-import android.net.util.NetdService;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -86,7 +85,9 @@
         mHandler = new Handler(mHandlerThread.getLooper());
 
         mContext = Objects.requireNonNull(context, "missing Context");
-        mNetd = Objects.requireNonNull(NetdService.getInstance(), "could not get netd instance");
+        mNetd = Objects.requireNonNull(
+                INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+                "could not get netd instance");
         mCm = mContext.getSystemService(ConnectivityManager.class);
         mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(),
                 TEST_NETWORK_PROVIDER_NAME);
diff --git a/service/src/com/android/server/connectivity/ConnectivityConstants.java b/service/src/com/android/server/connectivity/ConnectivityConstants.java
deleted file mode 100644
index 325a2cd..0000000
--- a/service/src/com/android/server/connectivity/ConnectivityConstants.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2018 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.connectivity;
-
-/**
- * A class encapsulating various constants used by Connectivity.
- * TODO : remove this class.
- * @hide
- */
-public class ConnectivityConstants {
-    // VPNs typically have priority over other networks. Give them a score that will
-    // let them win every single time.
-    public static final int VPN_DEFAULT_SCORE = 101;
-}
diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java
index 9326d69..6e38e2d 100644
--- a/service/src/com/android/server/connectivity/FullScore.java
+++ b/service/src/com/android/server/connectivity/FullScore.java
@@ -17,14 +17,20 @@
 package com.android.server.connectivity;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkScore.KEEP_CONNECTED_NONE;
+import static android.net.NetworkScore.POLICY_EXITING;
+import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY;
+import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkScore;
+import android.net.NetworkScore.KeepConnectedReason;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -50,7 +56,8 @@
             POLICY_IS_VALIDATED,
             POLICY_IS_VPN,
             POLICY_EVER_USER_SELECTED,
-            POLICY_ACCEPT_UNVALIDATED
+            POLICY_ACCEPT_UNVALIDATED,
+            POLICY_IS_UNMETERED
     })
     public @interface Policy {
     }
@@ -75,12 +82,22 @@
     /** @hide */
     public static final int POLICY_ACCEPT_UNVALIDATED = 60;
 
+    // This network is unmetered. {@see NetworkCapabilities.NET_CAPABILITY_NOT_METERED}.
+    /** @hide */
+    public static final int POLICY_IS_UNMETERED = 59;
+
     // To help iterate when printing
     @VisibleForTesting
-    static final int MIN_CS_MANAGED_POLICY = POLICY_ACCEPT_UNVALIDATED;
+    static final int MIN_CS_MANAGED_POLICY = POLICY_IS_UNMETERED;
     @VisibleForTesting
     static final int MAX_CS_MANAGED_POLICY = POLICY_IS_VALIDATED;
 
+    // Mask for policies in NetworkScore. This should have all bits managed by NetworkScore set
+    // and all bits managed by FullScore unset. As bits are handled from 0 up in NetworkScore and
+    // from 63 down in FullScore, cut at the 32rd bit for simplicity, but change this if some day
+    // there are more than 32 bits handled on either side.
+    private static final int EXTERNAL_POLICIES_MASK = 0x0000FFFF;
+
     @VisibleForTesting
     static @NonNull String policyNameOf(final int policy) {
         switch (policy) {
@@ -88,6 +105,10 @@
             case POLICY_IS_VPN: return "IS_VPN";
             case POLICY_EVER_USER_SELECTED: return "EVER_USER_SELECTED";
             case POLICY_ACCEPT_UNVALIDATED: return "ACCEPT_UNVALIDATED";
+            case POLICY_IS_UNMETERED: return "IS_UNMETERED";
+            case POLICY_YIELD_TO_BAD_WIFI: return "YIELD_TO_BAD_WIFI";
+            case POLICY_TRANSPORT_PRIMARY: return "TRANSPORT_PRIMARY";
+            case POLICY_EXITING: return "EXITING";
         }
         throw new IllegalArgumentException("Unknown policy : " + policy);
     }
@@ -95,9 +116,13 @@
     // Bitmask of all the policies applied to this score.
     private final long mPolicies;
 
-    FullScore(final int legacyInt, final long policies) {
+    private final int mKeepConnectedReason;
+
+    FullScore(final int legacyInt, final long policies,
+            @KeepConnectedReason final int keepConnectedReason) {
         mLegacyInt = legacyInt;
         mPolicies = policies;
+        mKeepConnectedReason = keepConnectedReason;
     }
 
     /**
@@ -106,18 +131,27 @@
      * @param score the score supplied by the agent
      * @param caps the NetworkCapabilities of the network
      * @param config the NetworkAgentConfig of the network
-     * @return an FullScore that is appropriate to use for ranking.
+     * @param yieldToBadWiFi whether this network yields to a previously validated wifi gone bad
+     * @return a FullScore that is appropriate to use for ranking.
      */
+    // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
+    // telephony factory, so that it depends on the carrier. For now this is handled by
+    // connectivity for backward compatibility.
     public static FullScore fromNetworkScore(@NonNull final NetworkScore score,
-            @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config) {
-        return withPolicies(score.getLegacyInt(), caps.hasCapability(NET_CAPABILITY_VALIDATED),
+            @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config,
+            final boolean yieldToBadWiFi) {
+        return withPolicies(score.getLegacyInt(), score.getPolicies(),
+                score.getKeepConnectedReason(),
+                caps.hasCapability(NET_CAPABILITY_VALIDATED),
                 caps.hasTransport(TRANSPORT_VPN),
+                caps.hasCapability(NET_CAPABILITY_NOT_METERED),
                 config.explicitlySelected,
-                config.acceptUnvalidated);
+                config.acceptUnvalidated,
+                yieldToBadWiFi);
     }
 
     /**
-     * Given a score supplied by the NetworkAgent, produce a prospective score for an offer.
+     * Given a score supplied by a NetworkProvider, produce a prospective score for an offer.
      *
      * NetworkOffers have score filters that are compared to the scores of actual networks
      * to see if they could possibly beat the current satisfier. Some things the agent can't
@@ -135,12 +169,17 @@
         final boolean mayValidate = caps.hasCapability(NET_CAPABILITY_INTERNET);
         // VPN transports are known in advance.
         final boolean vpn = caps.hasTransport(TRANSPORT_VPN);
+        // Prospective scores are always unmetered, because unmetered networks are stronger
+        // than metered networks, and it's not known in advance whether the network is metered.
+        final boolean unmetered = true;
         // The network hasn't been chosen by the user (yet, at least).
         final boolean everUserSelected = false;
         // Don't assume the user will accept unvalidated connectivity.
         final boolean acceptUnvalidated = false;
-        return withPolicies(score.getLegacyInt(), mayValidate, vpn, everUserSelected,
-                acceptUnvalidated);
+        // Don't assume clinging to bad wifi
+        final boolean yieldToBadWiFi = false;
+        return withPolicies(score.getLegacyInt(), score.getPolicies(), KEEP_CONNECTED_NONE,
+                mayValidate, vpn, unmetered, everUserSelected, acceptUnvalidated, yieldToBadWiFi);
     }
 
     /**
@@ -150,24 +189,40 @@
      * @param config the NetworkAgentConfig of the network
      * @return a score with the policies from the arguments reset
      */
+    // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
+    // telephony factory, so that it depends on the carrier. For now this is handled by
+    // connectivity for backward compatibility.
     public FullScore mixInScore(@NonNull final NetworkCapabilities caps,
-            @NonNull final NetworkAgentConfig config) {
-        return withPolicies(mLegacyInt, caps.hasCapability(NET_CAPABILITY_VALIDATED),
+            @NonNull final NetworkAgentConfig config, final boolean yieldToBadWifi) {
+        return withPolicies(mLegacyInt, mPolicies, mKeepConnectedReason,
+                caps.hasCapability(NET_CAPABILITY_VALIDATED),
                 caps.hasTransport(TRANSPORT_VPN),
+                caps.hasCapability(NET_CAPABILITY_NOT_METERED),
                 config.explicitlySelected,
-                config.acceptUnvalidated);
+                config.acceptUnvalidated,
+                yieldToBadWifi);
     }
 
+    // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
+    // telephony factory, so that it depends on the carrier. For now this is handled by
+    // connectivity for backward compatibility.
     private static FullScore withPolicies(@NonNull final int legacyInt,
+            final long externalPolicies,
+            @KeepConnectedReason final int keepConnectedReason,
             final boolean isValidated,
             final boolean isVpn,
+            final boolean isUnmetered,
             final boolean everUserSelected,
-            final boolean acceptUnvalidated) {
-        return new FullScore(legacyInt,
-                (isValidated         ? 1L << POLICY_IS_VALIDATED : 0)
+            final boolean acceptUnvalidated,
+            final boolean yieldToBadWiFi) {
+        return new FullScore(legacyInt, (externalPolicies & EXTERNAL_POLICIES_MASK)
+                | (isValidated       ? 1L << POLICY_IS_VALIDATED : 0)
                 | (isVpn             ? 1L << POLICY_IS_VPN : 0)
+                | (isUnmetered       ? 1L << POLICY_IS_UNMETERED : 0)
                 | (everUserSelected  ? 1L << POLICY_EVER_USER_SELECTED : 0)
-                | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0));
+                | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0)
+                | (yieldToBadWiFi    ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0),
+                keepConnectedReason);
     }
 
     /**
@@ -219,13 +274,21 @@
         return 0 != (mPolicies & (1L << policy));
     }
 
+    /**
+     * Returns the keep-connected reason, or KEEP_CONNECTED_NONE.
+     */
+    public int getKeepConnectedReason() {
+        return mKeepConnectedReason;
+    }
+
     // Example output :
     // Score(50 ; Policies : EVER_USER_SELECTED&IS_VALIDATED)
     @Override
     public String toString() {
         final StringJoiner sj = new StringJoiner(
                 "&", // delimiter
-                "Score(" + mLegacyInt + " ; Policies : ", // prefix
+                "Score(" + mLegacyInt + " ; KeepConnected : " + mKeepConnectedReason
+                        + " ; Policies : ", // prefix
                 ")"); // suffix
         for (int i = NetworkScore.MIN_AGENT_MANAGED_POLICY;
                 i <= NetworkScore.MAX_AGENT_MANAGED_POLICY; ++i) {
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index ee32fbf..5d793fd 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,6 +17,7 @@
 package com.android.server.connectivity;
 
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.transportNamesOf;
 
 import android.annotation.NonNull;
@@ -142,7 +143,7 @@
 // the network is no longer considered "lingering". After the linger timer expires, if the network
 // is satisfying one or more background NetworkRequests it is kept up in the background. If it is
 // not, ConnectivityService disconnects the NetworkAgent's AsyncChannel.
-public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
+public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRanker.Scoreable {
 
     @NonNull public NetworkInfo networkInfo;
     // This Network object should always be used if possible, so as to encourage reuse of the
@@ -362,9 +363,9 @@
         linkProperties = lp;
         networkCapabilities = nc;
         networkAgentConfig = config;
-        setScore(score); // uses members networkCapabilities and networkAgentConfig
-        clatd = new Nat464Xlat(this, netd, dnsResolver, deps);
         mConnService = connService;
+        setScore(score); // uses members connService, networkCapabilities and networkAgentConfig
+        clatd = new Nat464Xlat(this, netd, dnsResolver, deps);
         mContext = context;
         mHandler = handler;
         this.factorySerialNumber = factorySerialNumber;
@@ -706,7 +707,7 @@
             @NonNull final NetworkCapabilities nc) {
         final NetworkCapabilities oldNc = networkCapabilities;
         networkCapabilities = nc;
-        mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig);
+        mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, yieldToBadWiFi());
         final NetworkMonitorManager nm = mNetworkMonitor;
         if (nm != null) {
             nm.notifyNetworkCapabilitiesChanged(nc);
@@ -714,6 +715,11 @@
         return oldNc;
     }
 
+    private boolean yieldToBadWiFi() {
+        // Only cellular networks yield to bad wifi
+        return networkCapabilities.hasTransport(TRANSPORT_CELLULAR) && !mConnService.avoidBadWifi();
+    }
+
     public ConnectivityService connService() {
         return mConnService;
     }
@@ -884,13 +890,16 @@
         return isVPN();
     }
 
-    // Return true on devices configured to ignore score penalty for wifi networks
-    // that become unvalidated (b/31075769).
-    private boolean ignoreWifiUnvalidationPenalty() {
-        boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
-                networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        boolean avoidBadWifi = mConnService.avoidBadWifi() || avoidUnvalidated;
-        return isWifi && !avoidBadWifi && everValidated;
+    // Caller must not mutate. This method is called frequently and making a defensive copy
+    // would be too expensive. This is used by NetworkRanker.Scoreable, so it can be compared
+    // against other scoreables.
+    @Override public NetworkCapabilities getCaps() {
+        return networkCapabilities;
+    }
+
+    // NetworkRanker.Scoreable
+    @Override public FullScore getScore() {
+        return mScore;
     }
 
     // Get the current score for this Network.  This may be modified from what the
@@ -909,7 +918,8 @@
      * Mix-in the ConnectivityService-managed bits in the score.
      */
     public void setScore(final NetworkScore score) {
-        mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig);
+        mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig,
+                yieldToBadWiFi());
     }
 
     /**
@@ -918,7 +928,7 @@
      * Call this after updating the network agent config.
      */
     public void updateScoreForNetworkAgentConfigUpdate() {
-        mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig);
+        mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, yieldToBadWiFi());
     }
 
     /**
@@ -1081,7 +1091,7 @@
         return "NetworkAgentInfo{"
                 + "network{" + network + "}  handle{" + network.getNetworkHandle() + "}  ni{"
                 + networkInfo.toShortString() + "} "
-                + "  Score{" + getCurrentScore() + "} "
+                + mScore + " "
                 + (isNascent() ? " nascent" : (isLingering() ? " lingering" : ""))
                 + (everValidated ? " everValidated" : "")
                 + (lastValidated ? " lastValidated" : "")
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 0c0d459..b57ad5d 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -84,7 +84,7 @@
 
     // The context is for the current user (system server)
     private final Context mContext;
-    private final Resources mResources;
+    private final ConnectivityResources mResources;
     private final TelephonyManager mTelephonyManager;
     // The notification manager is created from a context for User.ALL, so notifications
     // will be sent to all users.
@@ -99,7 +99,7 @@
                 (NotificationManager) c.createContextAsUser(UserHandle.ALL, 0 /* flags */)
                         .getSystemService(Context.NOTIFICATION_SERVICE);
         mNotificationTypeMap = new SparseIntArray();
-        mResources = new ConnectivityResources(mContext).get();
+        mResources = new ConnectivityResources(mContext);
     }
 
     @VisibleForTesting
@@ -118,11 +118,11 @@
     }
 
     private String getTransportName(final int transportType) {
-        String[] networkTypes = mResources.getStringArray(R.array.network_switch_type_name);
+        String[] networkTypes = mResources.get().getStringArray(R.array.network_switch_type_name);
         try {
             return networkTypes[transportType];
         } catch (IndexOutOfBoundsException e) {
-            return mResources.getString(R.string.network_switch_type_name_unknown);
+            return mResources.get().getString(R.string.network_switch_type_name_unknown);
         }
     }
 
@@ -197,10 +197,11 @@
                     tag, nameOf(eventId), getTransportName(transportType), name, highPriority));
         }
 
-        final Resources r = mResources;
+        final Resources r = mResources.get();
         final CharSequence title;
         final CharSequence details;
-        Icon icon = Icon.createWithResource(r, getIcon(transportType));
+        Icon icon = Icon.createWithResource(
+                mResources.getResourcesContext(), getIcon(transportType));
         if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
             title = r.getString(R.string.wifi_no_internet, name);
             details = r.getString(R.string.wifi_no_internet_detailed);
@@ -355,7 +356,7 @@
     public void showToast(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) {
         String fromTransport = getTransportName(approximateTransportType(fromNai));
         String toTransport = getTransportName(approximateTransportType(toNai));
-        String text = mResources.getString(
+        String text = mResources.get().getString(
                 R.string.network_switch_metered_toast, fromTransport, toTransport);
         Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
     }
diff --git a/service/src/com/android/server/connectivity/NetworkOffer.java b/service/src/com/android/server/connectivity/NetworkOffer.java
index fa2d465..5336593 100644
--- a/service/src/com/android/server/connectivity/NetworkOffer.java
+++ b/service/src/com/android/server/connectivity/NetworkOffer.java
@@ -17,14 +17,14 @@
 package com.android.server.connectivity;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.INetworkOfferCallback;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.os.Messenger;
+import android.os.RemoteException;
 
+import java.util.HashSet;
 import java.util.Objects;
-
+import java.util.Set;
 
 /**
  * Represents an offer made by a NetworkProvider to create a network if a need arises.
@@ -40,27 +40,88 @@
  *
  * @hide
  */
-public class NetworkOffer {
+public class NetworkOffer implements NetworkRanker.Scoreable {
     @NonNull public final FullScore score;
     @NonNull public final NetworkCapabilities caps;
     @NonNull public final INetworkOfferCallback callback;
-    @NonNull public final Messenger provider;
+    @NonNull public final int providerId;
+    // While this could, in principle, be deduced from the old values of the satisfying networks,
+    // doing so would add a lot of complexity and performance penalties. For each request, the
+    // ranker would have to run again to figure out if this offer used to be able to beat the
+    // previous satisfier to know if there is a change in whether this offer is now needed ;
+    // besides, there would be a need to handle an edge case when a new request comes online,
+    // where it's not satisfied before the first rematch, where starting to satisfy a request
+    // should not result in sending unneeded to this offer. This boolean, while requiring that
+    // the offers are only ever manipulated on the CS thread, is by far a simpler and
+    // economical solution.
+    private final Set<NetworkRequest> mCurrentlyNeeded = new HashSet<>();
 
-    private static NetworkCapabilities emptyCaps() {
-        final NetworkCapabilities nc = new NetworkCapabilities();
-        return nc;
+    public NetworkOffer(@NonNull final FullScore score,
+            @NonNull final NetworkCapabilities caps,
+            @NonNull final INetworkOfferCallback callback,
+            @NonNull final int providerId) {
+        this.score = Objects.requireNonNull(score);
+        this.caps = Objects.requireNonNull(caps);
+        this.callback = Objects.requireNonNull(callback);
+        this.providerId = providerId;
     }
 
-    // Ideally the caps argument would be non-null, but null has historically meant no filter
-    // and telephony passes null. Keep backward compatibility.
-    public NetworkOffer(@NonNull final FullScore score,
-            @Nullable final NetworkCapabilities caps,
-            @NonNull final INetworkOfferCallback callback,
-            @NonNull final Messenger provider) {
-        this.score = Objects.requireNonNull(score);
-        this.caps = null != caps ? caps : emptyCaps();
-        this.callback = Objects.requireNonNull(callback);
-        this.provider = Objects.requireNonNull(provider);
+    /**
+     * Get the score filter of this offer
+     */
+    @Override @NonNull public FullScore getScore() {
+        return score;
+    }
+
+    /**
+     * Get the capabilities filter of this offer
+     */
+    @Override @NonNull public NetworkCapabilities getCaps() {
+        return caps;
+    }
+
+    /**
+     * Tell the provider for this offer that the network is needed for a request.
+     * @param request the request for which the offer is needed
+     */
+    public void onNetworkNeeded(@NonNull final NetworkRequest request) {
+        if (mCurrentlyNeeded.contains(request)) {
+            throw new IllegalStateException("Network already needed");
+        }
+        mCurrentlyNeeded.add(request);
+        try {
+            callback.onNetworkNeeded(request);
+        } catch (final RemoteException e) {
+            // The provider is dead. It will be removed by the death recipient.
+        }
+    }
+
+    /**
+     * Tell the provider for this offer that the network is no longer needed for this request.
+     *
+     * onNetworkNeeded will have been called with the same request before.
+     *
+     * @param request the request
+     */
+    public void onNetworkUnneeded(@NonNull final NetworkRequest request) {
+        if (!mCurrentlyNeeded.contains(request)) {
+            throw new IllegalStateException("Network already unneeded");
+        }
+        mCurrentlyNeeded.remove(request);
+        try {
+            callback.onNetworkUnneeded(request);
+        } catch (final RemoteException e) {
+            // The provider is dead. It will be removed by the death recipient.
+        }
+    }
+
+    /**
+     * Returns whether this offer is currently needed for this request.
+     * @param request the request
+     * @return whether the offer is currently considered needed
+     */
+    public boolean neededFor(@NonNull final NetworkRequest request) {
+        return mCurrentlyNeeded.contains(request);
     }
 
     /**
@@ -69,22 +130,15 @@
      * When an updated offer is sent from a provider, call this method on the new offer, passing
      * the old one, to take over the state.
      *
-     * @param previousOffer
+     * @param previousOffer the previous offer
      */
     public void migrateFrom(@NonNull final NetworkOffer previousOffer) {
         if (!callback.equals(previousOffer.callback)) {
             throw new IllegalArgumentException("Can only migrate from a previous version of"
                     + " the same offer");
         }
-    }
-
-    /**
-     * Returns whether an offer can satisfy a NetworkRequest, according to its capabilities.
-     * @param request The request to test against.
-     * @return Whether this offer can satisfy the request.
-     */
-    public final boolean canSatisfy(@NonNull final NetworkRequest request) {
-        return request.networkCapabilities.satisfiedByNetworkCapabilities(caps);
+        mCurrentlyNeeded.clear();
+        mCurrentlyNeeded.addAll(previousOffer.mCurrentlyNeeded);
     }
 
     @Override
diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java
index d0aabf9..698744a 100644
--- a/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -16,30 +16,223 @@
 
 package com.android.server.connectivity;
 
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkScore.POLICY_EXITING;
+import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY;
+import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI;
+
+import static com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED;
+import static com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED;
+import static com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED;
+import static com.android.server.connectivity.FullScore.POLICY_IS_VPN;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 
+import com.android.net.module.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * A class that knows how to find the best network matching a request out of a list of networks.
  */
 public class NetworkRanker {
+    /**
+     * A class that can be scored against other scoreables.
+     */
+    public interface Scoreable {
+        /** Get score of this scoreable */
+        FullScore getScore();
+        /** Get capabilities of this scoreable */
+        NetworkCapabilities getCaps();
+    }
+
+    private static final boolean USE_POLICY_RANKING = false;
+
     public NetworkRanker() { }
 
+    // TODO : move to module utils CollectionUtils.
+    @NonNull private static <T> ArrayList<T> filter(@NonNull final Collection<T> source,
+            @NonNull final Predicate<T> test) {
+        final ArrayList<T> matches = new ArrayList<>();
+        for (final T e : source) {
+            if (test.test(e)) {
+                matches.add(e);
+            }
+        }
+        return matches;
+    }
+
+
     /**
      * Find the best network satisfying this request among the list of passed networks.
      */
-    // Almost equivalent to Collections.max(nais), but allows returning null if no network
-    // satisfies the request.
     @Nullable
     public NetworkAgentInfo getBestNetwork(@NonNull final NetworkRequest request,
+            @NonNull final Collection<NetworkAgentInfo> nais,
+            @Nullable final NetworkAgentInfo currentSatisfier) {
+        final ArrayList<NetworkAgentInfo> candidates = filter(nais, nai -> nai.satisfies(request));
+        if (candidates.size() == 1) return candidates.get(0); // Only one potential satisfier
+        if (candidates.size() <= 0) return null; // No network can satisfy this request
+        if (USE_POLICY_RANKING) {
+            return getBestNetworkByPolicy(candidates, currentSatisfier);
+        } else {
+            return getBestNetworkByLegacyInt(candidates);
+        }
+    }
+
+    // Transport preference order, if it comes down to that.
+    private static final int[] PREFERRED_TRANSPORTS_ORDER = { TRANSPORT_ETHERNET, TRANSPORT_WIFI,
+            TRANSPORT_BLUETOOTH, TRANSPORT_CELLULAR };
+
+    // Function used to partition a list into two working areas depending on whether they
+    // satisfy a predicate. All items satisfying the predicate will be put in |positive|, all
+    // items that don't will be put in |negative|.
+    // This is useful in this file because many of the ranking checks will retain only networks that
+    // satisfy a predicate if any of them do, but keep them all if all of them do. Having working
+    // areas is uncustomary in Java, but this function is called in a fairly intensive manner
+    // and doing allocation quite that often might affect performance quite badly.
+    private static <T> void partitionInto(@NonNull final List<T> source, @NonNull Predicate<T> test,
+            @NonNull final List<T> positive, @NonNull final List<T> negative) {
+        positive.clear();
+        negative.clear();
+        for (final T item : source) {
+            if (test.test(item)) {
+                positive.add(item);
+            } else {
+                negative.add(item);
+            }
+        }
+    }
+
+    @Nullable private <T extends Scoreable> T getBestNetworkByPolicy(
+            @NonNull List<T> candidates,
+            @Nullable final T currentSatisfier) {
+        // Used as working areas.
+        final ArrayList<T> accepted =
+                new ArrayList<>(candidates.size() /* initialCapacity */);
+        final ArrayList<T> rejected =
+                new ArrayList<>(candidates.size() /* initialCapacity */);
+
+        // The following tests will search for a network matching a given criterion. They all
+        // function the same way : if any network matches the criterion, drop from consideration
+        // all networks that don't. To achieve this, the tests below :
+        // 1. partition the list of remaining candidates into accepted and rejected networks.
+        // 2. if only one candidate remains, that's the winner : if accepted.size == 1 return [0]
+        // 3. if multiple remain, keep only the accepted networks and go on to the next criterion.
+        //    Because the working areas will be wiped, a copy of the accepted networks needs to be
+        //    made.
+        // 4. if none remain, the criterion did not help discriminate so keep them all. As an
+        //    optimization, skip creating a new array and go on to the next criterion.
+
+        // If there is a connected VPN, use it.
+        partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VPN),
+                accepted, rejected);
+        if (accepted.size() == 1) return accepted.get(0);
+        if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // Selected & Accept-unvalidated policy : if any network has both of these, then don't
+        // choose one that doesn't.
+        partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_EVER_USER_SELECTED)
+                        && nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED),
+                accepted, rejected);
+        if (accepted.size() == 1) return accepted.get(0);
+        if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // Yield to bad wifi policy : if any wifi has ever been validated, keep only networks
+        // that don't yield to such a wifi network.
+        final boolean anyWiFiEverValidated = CollectionUtils.any(candidates,
+                nai -> nai.getScore().hasPolicy(POLICY_EVER_USER_SELECTED)
+                        && nai.getCaps().hasTransport(TRANSPORT_WIFI));
+        if (anyWiFiEverValidated) {
+            partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI),
+                    accepted, rejected);
+            if (accepted.size() == 1) return accepted.get(0);
+            if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+        }
+
+        // If any network is validated (or should be accepted even if it's not validated), then
+        // don't choose one that isn't.
+        partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VALIDATED)
+                        || nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED),
+                accepted, rejected);
+        if (accepted.size() == 1) return accepted.get(0);
+        if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // If any network is not exiting, don't choose one that is.
+        partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_EXITING),
+                accepted, rejected);
+        if (accepted.size() == 1) return accepted.get(0);
+        if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // TODO : If any network is unmetered, don't choose a metered network.
+        // This can't be implemented immediately because prospective networks are always
+        // considered unmetered because factories don't know if the network will be metered.
+        // Saying an unmetered network always beats a metered one would mean that when metered wifi
+        // is connected, the offer for telephony would beat WiFi but the actual metered network
+        // would lose, so we'd have an infinite loop where telephony would continually bring up
+        // a network that is immediately torn down.
+        // Fix this by getting the agent to tell connectivity whether the network they will
+        // bring up is metered. Cell knows that in advance, while WiFi has a good estimate and
+        // can revise it if the network later turns out to be metered.
+        // partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_UNMETERED),
+        //         accepted, rejected);
+        // if (accepted.size() == 1) return accepted.get(0);
+        // if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // If any network is for the default subscription, don't choose a network for another
+        // subscription with the same transport.
+        partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY),
+                accepted, rejected);
+        for (final Scoreable defaultSubNai : accepted) {
+            // Remove all networks without the DEFAULT_SUBSCRIPTION policy and the same transports
+            // as a network that has it.
+            final int[] transports = defaultSubNai.getCaps().getTransportTypes();
+            candidates.removeIf(nai -> !nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY)
+                    && Arrays.equals(transports, nai.getCaps().getTransportTypes()));
+        }
+        if (1 == candidates.size()) return candidates.get(0);
+        // It's guaranteed candidates.size() > 0 because there is at least one with DEFAULT_SUB
+        // policy and only those without it were removed.
+
+        // If some of the networks have a better transport than others, keep only the ones with
+        // the best transports.
+        for (final int transport : PREFERRED_TRANSPORTS_ORDER) {
+            partitionInto(candidates, nai -> nai.getCaps().hasTransport(transport),
+                    accepted, rejected);
+            if (accepted.size() == 1) return accepted.get(0);
+            if (accepted.size() > 0 && rejected.size() > 0) {
+                candidates = new ArrayList<>(accepted);
+                break;
+            }
+        }
+
+        // At this point there are still multiple networks passing all the tests above. If any
+        // of them is the previous satisfier, keep it.
+        if (candidates.contains(currentSatisfier)) return currentSatisfier;
+
+        // If there are still multiple options at this point but none of them is any of the
+        // transports above, it doesn't matter which is returned. They are all the same.
+        return candidates.get(0);
+    }
+
+    // TODO : switch to the policy implementation and remove
+    // Almost equivalent to Collections.max(nais), but allows returning null if no network
+    // satisfies the request.
+    private NetworkAgentInfo getBestNetworkByLegacyInt(
             @NonNull final Collection<NetworkAgentInfo> nais) {
         NetworkAgentInfo bestNetwork = null;
         int bestScore = Integer.MIN_VALUE;
         for (final NetworkAgentInfo nai : nais) {
-            if (!nai.satisfies(request)) continue;
             if (nai.getCurrentScore() > bestScore) {
                 bestNetwork = nai;
                 bestScore = nai.getCurrentScore();
@@ -47,4 +240,59 @@
         }
         return bestNetwork;
     }
+
+    /**
+     * Returns whether an offer has a chance to beat a champion network for a request.
+     *
+     * Offers are sent by network providers when they think they might be able to make a network
+     * with the characteristics contained in the offer. If the offer has no chance to beat
+     * the currently best network for a given request, there is no point in the provider spending
+     * power trying to find and bring up such a network.
+     *
+     * Note that having an offer up does not constitute a commitment from the provider part
+     * to be able to bring up a network with these characteristics, or a network at all for
+     * that matter. This is only used to save power by letting providers know when they can't
+     * beat a current champion.
+     *
+     * @param request The request to evaluate against.
+     * @param champion The currently best network for this request.
+     * @param offer The offer.
+     * @return Whether the offer stands a chance to beat the champion.
+     */
+    public boolean mightBeat(@NonNull final NetworkRequest request,
+            @Nullable final NetworkAgentInfo champion,
+            @NonNull final NetworkOffer offer) {
+        // If this network can't even satisfy the request then it can't beat anything, not
+        // even an absence of network. It can't satisfy it anyway.
+        if (!request.canBeSatisfiedBy(offer.caps)) return false;
+        // If there is no satisfying network, then this network can beat, because some network
+        // is always better than no network.
+        if (null == champion) return true;
+        if (USE_POLICY_RANKING) {
+            // If there is no champion, the offer can always beat.
+            // Otherwise rank them.
+            final ArrayList<Scoreable> candidates = new ArrayList<>();
+            candidates.add(champion);
+            candidates.add(offer);
+            return offer == getBestNetworkByPolicy(candidates, champion);
+        } else {
+            return mightBeatByLegacyInt(request, champion.getScore(), offer);
+        }
+    }
+
+    /**
+     * Returns whether an offer might beat a champion according to the legacy int.
+     */
+    public boolean mightBeatByLegacyInt(@NonNull final NetworkRequest request,
+            @Nullable final FullScore championScore,
+            @NonNull final NetworkOffer offer) {
+        final int offerIntScore;
+        if (offer.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+            // If the offer might have Internet access, then it might validate.
+            offerIntScore = offer.score.getLegacyIntAsValidated();
+        } else {
+            offerIntScore = offer.score.getLegacyInt();
+        }
+        return championScore.getLegacyInt() < offerIntScore;
+    }
 }
diff --git a/service/src/com/android/server/connectivity/OsCompat.java b/service/src/com/android/server/connectivity/OsCompat.java
new file mode 100644
index 0000000..57e3dcd
--- /dev/null
+++ b/service/src/com/android/server/connectivity/OsCompat.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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.connectivity;
+
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.FileDescriptor;
+
+/**
+ * Compatibility utility for android.system.Os core platform APIs.
+ *
+ * Connectivity has access to such APIs, but they are not part of the module_current stubs yet
+ * (only core_current). Most stable core platform APIs are included manually in the connectivity
+ * build rules, but because Os is also part of the base java SDK that is earlier on the
+ * classpath, the extra core platform APIs are not seen.
+ *
+ * TODO (b/157639992, b/183097033): remove as soon as core_current is part of system_server_current
+ * @hide
+ */
+public class OsCompat {
+    // This value should be correct on all architectures supported by Android, but hardcoding ioctl
+    // numbers should be avoided.
+    /**
+     * @see android.system.OsConstants#TIOCOUTQ
+     */
+    public static final int TIOCOUTQ = 0x5411;
+
+    /**
+     * @see android.system.Os#getsockoptInt(FileDescriptor, int, int)
+     */
+    public static int getsockoptInt(FileDescriptor fd, int level, int option) throws
+            ErrnoException {
+        try {
+            return (int) Os.class.getMethod(
+                    "getsockoptInt", FileDescriptor.class, int.class, int.class)
+                    .invoke(null, fd, level, option);
+        } catch (ReflectiveOperationException e) {
+            if (e.getCause() instanceof ErrnoException) {
+                throw (ErrnoException) e.getCause();
+            }
+            throw new IllegalStateException("Error calling getsockoptInt", e);
+        }
+    }
+
+    /**
+     * @see android.system.Os#ioctlInt(FileDescriptor, int)
+     */
+    public static int ioctlInt(FileDescriptor fd, int cmd) throws
+            ErrnoException {
+        try {
+            return (int) Os.class.getMethod(
+                    "ioctlInt", FileDescriptor.class, int.class).invoke(null, fd, cmd);
+        } catch (ReflectiveOperationException e) {
+            if (e.getCause() instanceof ErrnoException) {
+                throw (ErrnoException) e.getCause();
+            }
+            throw new IllegalStateException("Error calling ioctlInt", e);
+        }
+    }
+}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 673c804..49b43f8 100644
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -24,7 +24,7 @@
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
-import static android.net.ConnectivitySettingsManager.APPS_ALLOWED_ON_RESTRICTED_NETWORKS;
+import static android.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
@@ -109,13 +109,13 @@
     @GuardedBy("this")
     private final Set<Integer> mAllApps = new HashSet<>();
 
-    // A set of apps which are allowed to use restricted networks. These apps can't hold the
-    // CONNECTIVITY_USE_RESTRICTED_NETWORKS permission because they can't be signature|privileged
-    // apps. However, these apps should still be able to use restricted networks under certain
-    // conditions (e.g. government app using emergency services). So grant netd system permission
-    // to uids whose package name is listed in APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting.
+    // A set of uids which are allowed to use restricted networks. The packages of these uids can't
+    // hold the CONNECTIVITY_USE_RESTRICTED_NETWORKS permission because they can't be
+    // signature|privileged apps. However, these apps should still be able to use restricted
+    // networks under certain conditions (e.g. government app using emergency services). So grant
+    // netd system permission to these uids which is listed in UIDS_ALLOWED_ON_RESTRICTED_NETWORKS.
     @GuardedBy("this")
-    private final Set<String> mAppsAllowedOnRestrictedNetworks = new ArraySet<>();
+    private final Set<Integer> mUidsAllowedOnRestrictedNetworks = new ArraySet<>();
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -149,10 +149,10 @@
         }
 
         /**
-         * Get apps allowed to use restricted networks via ConnectivitySettingsManager.
+         * Get uids allowed to use restricted networks via ConnectivitySettingsManager.
          */
-        public Set<String> getAppsAllowedOnRestrictedNetworks(@NonNull Context context) {
-            return ConnectivitySettingsManager.getAppsAllowedOnRestrictedNetworks(context);
+        public Set<Integer> getUidsAllowedOnRestrictedNetworks(@NonNull Context context) {
+            return ConnectivitySettingsManager.getUidsAllowedOnRestrictedNetworks(context);
         }
 
         /**
@@ -194,10 +194,10 @@
                 mIntentReceiver, intentFilter, null /* broadcastPermission */,
                 null /* scheduler */);
 
-        // Register APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting observer
+        // Register UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting observer
         mDeps.registerContentObserver(
                 userAllContext,
-                Settings.Secure.getUriFor(APPS_ALLOWED_ON_RESTRICTED_NETWORKS),
+                Settings.Secure.getUriFor(UIDS_ALLOWED_ON_RESTRICTED_NETWORKS),
                 false /* notifyForDescendants */,
                 new ContentObserver(null) {
                     @Override
@@ -206,9 +206,9 @@
                     }
                 });
 
-        // Read APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting and update
-        // mAppsAllowedOnRestrictedNetworks.
-        updateAppsAllowedOnRestrictedNetworks(mDeps.getAppsAllowedOnRestrictedNetworks(mContext));
+        // Read UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting and update
+        // mUidsAllowedOnRestrictedNetworks.
+        updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext));
 
         List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS
                 | MATCH_ANY_USER);
@@ -265,9 +265,9 @@
     }
 
     @VisibleForTesting
-    void updateAppsAllowedOnRestrictedNetworks(final Set<String> apps) {
-        mAppsAllowedOnRestrictedNetworks.clear();
-        mAppsAllowedOnRestrictedNetworks.addAll(apps);
+    void updateUidsAllowedOnRestrictedNetworks(final Set<Integer> uids) {
+        mUidsAllowedOnRestrictedNetworks.clear();
+        mUidsAllowedOnRestrictedNetworks.addAll(uids);
     }
 
     @VisibleForTesting
@@ -285,10 +285,11 @@
     }
 
     @VisibleForTesting
-    boolean isAppAllowedOnRestrictedNetworks(@NonNull final PackageInfo app) {
-        // Check whether package name is in allowed on restricted networks app list. If so, this app
-        // can have netd system permission.
-        return mAppsAllowedOnRestrictedNetworks.contains(app.packageName);
+    boolean isUidAllowedOnRestrictedNetworks(final ApplicationInfo appInfo) {
+        if (appInfo == null) return false;
+        // Check whether package's uid is in allowed on restricted networks uid list. If so, this
+        // uid can have netd system permission.
+        return mUidsAllowedOnRestrictedNetworks.contains(appInfo.uid);
     }
 
     @VisibleForTesting
@@ -310,7 +311,8 @@
     boolean hasRestrictedNetworkPermission(@NonNull final PackageInfo app) {
         // TODO : remove carryover package check in the future(b/31479477). All apps should just
         //  request the appropriate permission for their use case since android Q.
-        return isCarryoverPackage(app.applicationInfo) || isAppAllowedOnRestrictedNetworks(app)
+        return isCarryoverPackage(app.applicationInfo)
+                || isUidAllowedOnRestrictedNetworks(app.applicationInfo)
                 || hasPermission(app, PERMISSION_MAINLINE_NETWORK_STACK)
                 || hasPermission(app, NETWORK_STACK)
                 || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
@@ -770,35 +772,31 @@
     }
 
     private synchronized void onSettingChanged() {
-        // Step1. Update apps allowed to use restricted networks and compute the set of packages to
+        // Step1. Update uids allowed to use restricted networks and compute the set of uids to
         // update.
-        final Set<String> packagesToUpdate = new ArraySet<>(mAppsAllowedOnRestrictedNetworks);
-        updateAppsAllowedOnRestrictedNetworks(mDeps.getAppsAllowedOnRestrictedNetworks(mContext));
-        packagesToUpdate.addAll(mAppsAllowedOnRestrictedNetworks);
+        final Set<Integer> uidsToUpdate = new ArraySet<>(mUidsAllowedOnRestrictedNetworks);
+        updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext));
+        uidsToUpdate.addAll(mUidsAllowedOnRestrictedNetworks);
 
-        final Map<Integer, Boolean> updatedApps = new HashMap<>();
-        final Map<Integer, Boolean> removedApps = new HashMap<>();
+        final Map<Integer, Boolean> updatedUids = new HashMap<>();
+        final Map<Integer, Boolean> removedUids = new HashMap<>();
 
-        // Step2. For each package to update, find out its new permission.
-        for (String app : packagesToUpdate) {
-            final PackageInfo info = getPackageInfo(app);
-            if (info == null || info.applicationInfo == null) continue;
-
-            final int uid = info.applicationInfo.uid;
+        // Step2. For each uid to update, find out its new permission.
+        for (Integer uid : uidsToUpdate) {
             final Boolean permission = highestUidNetworkPermission(uid);
 
             if (null == permission) {
-                removedApps.put(uid, NETWORK); // Doesn't matter which permission is set here.
+                removedUids.put(uid, NETWORK); // Doesn't matter which permission is set here.
                 mApps.remove(uid);
             } else {
-                updatedApps.put(uid, permission);
+                updatedUids.put(uid, permission);
                 mApps.put(uid, permission);
             }
         }
 
         // Step3. Update or revoke permission for uids with netd.
-        update(mUsers, updatedApps, true /* add */);
-        update(mUsers, removedApps, false /* add */);
+        update(mUsers, updatedUids, true /* add */);
+        update(mUsers, removedUids, false /* add */);
     }
 
     /** Dump info to dumpsys */
diff --git a/service/src/com/android/server/connectivity/TcpKeepaliveController.java b/service/src/com/android/server/connectivity/TcpKeepaliveController.java
index c480594..73f3475 100644
--- a/service/src/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/service/src/com/android/server/connectivity/TcpKeepaliveController.java
@@ -27,7 +27,8 @@
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IP_TOS;
 import static android.system.OsConstants.IP_TTL;
-import static android.system.OsConstants.TIOCOUTQ;
+
+import static com.android.server.connectivity.OsCompat.TIOCOUTQ;
 
 import android.annotation.NonNull;
 import android.net.InvalidPacketException;
@@ -175,10 +176,10 @@
             }
             // Query write sequence number from SEND_QUEUE.
             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
-            tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+            tcpDetails.seq = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
             // Query read sequence number from RECV_QUEUE.
             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
-            tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+            tcpDetails.ack = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
             // Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
             // Finally, check if socket is still idle. TODO : this check needs to move to
@@ -198,9 +199,9 @@
             tcpDetails.rcvWndScale = trw.rcvWndScale;
             if (tcpDetails.srcAddress.length == 4 /* V4 address length */) {
                 // Query TOS.
-                tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
+                tcpDetails.tos = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
                 // Query TTL.
-                tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
+                tcpDetails.ttl = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
             }
         } catch (ErrnoException e) {
             Log.e(TAG, "Exception reading TCP state from socket", e);
@@ -305,7 +306,7 @@
 
     private static boolean isReceiveQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        final int result = Os.ioctlInt(fd, SIOCINQ);
+        final int result = OsCompat.ioctlInt(fd, SIOCINQ);
         if (result != 0) {
             Log.e(TAG, "Read queue has data");
             return false;
@@ -315,7 +316,7 @@
 
     private static boolean isSendQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        final int result = Os.ioctlInt(fd, SIOCOUTQ);
+        final int result = OsCompat.ioctlInt(fd, SIOCOUTQ);
         if (result != 0) {
             Log.e(TAG, "Write queue has data");
             return false;
diff --git a/tests/OWNERS b/tests/OWNERS
deleted file mode 100644
index d3836d4..0000000
--- a/tests/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-set noparent
-
-codewiz@google.com
-jchalard@google.com
-junyulai@google.com
-lorenzo@google.com
-reminv@google.com
-satk@google.com
diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt
index 340e6f9..7424157 100644
--- a/tests/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/common/java/android/net/NetworkProviderTest.kt
@@ -198,4 +198,4 @@
         cb.expectCallback<OnUnavailable>() { nr.getNetworkSpecifier() == specifier }
         mCm.unregisterNetworkProvider(provider)
     }
-}
\ No newline at end of file
+}
diff --git a/tests/cts/net/jni/Android.bp b/tests/cts/net/jni/Android.bp
index 13f38d7..8f0d78f 100644
--- a/tests/cts/net/jni/Android.bp
+++ b/tests/cts/net/jni/Android.bp
@@ -16,35 +16,14 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_library_shared {
-    name: "libnativedns_jni",
+cc_defaults {
+    name: "net_jni_defaults",
 
-    srcs: ["NativeDnsJni.c"],
-    sdk_version: "current",
-
-    shared_libs: [
-        "libnativehelper_compat_libc++",
-        "liblog",
-    ],
-    stl: "libc++_static",
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-    ],
-
-}
-
-cc_library_shared {
-    name: "libnativemultinetwork_jni",
-
-    srcs: ["NativeMultinetworkJni.cpp"],
-    sdk_version: "current",
     cflags: [
         "-Wall",
         "-Werror",
         "-Wno-format",
+        "-Wno-unused-parameter",
     ],
     shared_libs: [
         "libandroid",
@@ -52,4 +31,19 @@
         "liblog",
     ],
     stl: "libc++_static",
+    // To be compatible with Q devices, the min_sdk_version must be 29.
+    sdk_version: "current",
+    min_sdk_version: "29",
+}
+
+cc_library_shared {
+    name: "libnativedns_jni",
+    defaults: ["net_jni_defaults"],
+    srcs: ["NativeDnsJni.c"],
+}
+
+cc_library_shared {
+    name: "libnativemultinetwork_jni",
+    defaults: ["net_jni_defaults"],
+    srcs: ["NativeMultinetworkJni.cpp"],
 }
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index 5e9af8e..434e529 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -24,6 +24,8 @@
         "liblog",
         "libutils",
     ],
+    // To be compatible with Q devices, the min_sdk_version must be 29.
+    min_sdk_version: "29",
 }
 
 cc_test {
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 8023aa0..33f704f 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -49,11 +49,16 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TetheringRequest;
 import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
 import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
 import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION;
 import static android.net.cts.util.CtsNetUtils.TEST_HOST;
 import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+import static android.net.cts.util.CtsTetheringUtils.StartTetheringCallback;
+import static android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
+import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
 import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
 import static android.system.OsConstants.AF_INET;
@@ -90,6 +95,7 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivitySettingsManager;
 import android.net.InetAddresses;
 import android.net.IpSecManager;
 import android.net.IpSecManager.UdpEncapsulationSocket;
@@ -106,6 +112,7 @@
 import android.net.SocketKeepalive;
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkManager;
+import android.net.TetheringManager;
 import android.net.cts.util.CtsNetUtils;
 import android.net.util.KeepaliveUtils;
 import android.net.wifi.WifiManager;
@@ -220,6 +227,9 @@
     private static final LinkAddress TEST_LINKADDR = new LinkAddress(
             InetAddresses.parseNumericAddress("2001:db8::8"), 64);
 
+    private static final int AIRPLANE_MODE_OFF = 0;
+    private static final int AIRPLANE_MODE_ON = 1;
+
     private Context mContext;
     private Instrumentation mInstrumentation;
     private ConnectivityManager mCm;
@@ -229,6 +239,7 @@
     private final ArraySet<Integer> mNetworkTypes = new ArraySet<>();
     private UiAutomation mUiAutomation;
     private CtsNetUtils mCtsNetUtils;
+    private TetheringManager mTm;
 
     // Used for cleanup purposes.
     private final List<Range<Integer>> mVpnRequiredUidRanges = new ArrayList<>();
@@ -242,6 +253,7 @@
         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
         mPackageManager = mContext.getPackageManager();
         mCtsNetUtils = new CtsNetUtils(mContext);
+        mTm = mContext.getSystemService(TetheringManager.class);
 
         if (DevSdkIgnoreRuleKt.isDevSdkInRange(null /* minExclusive */,
                 Build.VERSION_CODES.R /* maxInclusive */)) {
@@ -1922,4 +1934,76 @@
         assertThrows(SecurityException.class, () -> mCm.setGlobalProxy(
                 ProxyInfo.buildDirectProxy("example.com" /* host */, 8080 /* port */)));
     }
+
+    @Test
+    public void testFactoryResetWithoutPermission() {
+        assumeTrue(TestUtils.shouldTestSApis());
+        assertThrows(SecurityException.class, () -> mCm.factoryReset());
+    }
+
+    @Test
+    public void testFactoryReset() throws Exception {
+        assumeTrue(TestUtils.shouldTestSApis());
+
+        // Store current settings.
+        final int curAvoidBadWifi =
+                ConnectivitySettingsManager.getNetworkAvoidBadWifi(mContext);
+        final int curPrivateDnsMode = ConnectivitySettingsManager.getPrivateDnsMode(mContext);
+
+        final TestTetheringEventCallback tetherEventCallback = new TestTetheringEventCallback();
+        try {
+            mTm.registerTetheringEventCallback(c -> c.run() /* executor */, tetherEventCallback);
+            // Adopt for NETWORK_SETTINGS permission.
+            mUiAutomation.adoptShellPermissionIdentity();
+            // start tethering
+            tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            startWifiTethering(tetherEventCallback);
+            // Update setting to verify the behavior.
+            mCm.setAirplaneMode(true);
+            ConnectivitySettingsManager.setPrivateDnsMode(mContext,
+                    ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF);
+            ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext,
+                    ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE);
+            assertEquals(AIRPLANE_MODE_ON, Settings.Global.getInt(
+                    mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON));
+            // Verify factoryReset
+            mCm.factoryReset();
+            verifySettings(AIRPLANE_MODE_OFF,
+                    ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC,
+                    ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_PROMPT);
+
+            tetherEventCallback.expectNoTetheringActive();
+        } finally {
+            // Restore settings.
+            mCm.setAirplaneMode(false);
+            ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext, curAvoidBadWifi);
+            ConnectivitySettingsManager.setPrivateDnsMode(mContext, curPrivateDnsMode);
+            mTm.unregisterTetheringEventCallback(tetherEventCallback);
+            mTm.stopAllTethering();
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private void verifySettings(int expectedAirplaneMode, int expectedPrivateDnsMode,
+            int expectedAvoidBadWifi) throws Exception {
+        assertEquals(expectedAirplaneMode, Settings.Global.getInt(
+                mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON));
+        assertEquals(expectedPrivateDnsMode,
+                ConnectivitySettingsManager.getPrivateDnsMode(mContext));
+        assertEquals(expectedAvoidBadWifi,
+                ConnectivitySettingsManager.getNetworkAvoidBadWifi(mContext));
+    }
+
+    private void startWifiTethering(final TestTetheringEventCallback callback) throws Exception {
+        if (!isWifiTetheringSupported(mContext, callback)) return;
+
+        final List<String> wifiRegexs =
+                callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
+        final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+        final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+                .setShouldShowEntitlementUi(false).build();
+        mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
+        startTetheringCallback.verifyTetheringStarted();
+        callback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI);
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 8c35b97..63863da 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -30,6 +30,7 @@
 
 import static junit.framework.Assert.fail;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -418,6 +419,21 @@
         }
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testGetCapabilities() {
+        final int[] netCapabilities = new int[] {
+                NET_CAPABILITY_INTERNET,
+                NET_CAPABILITY_NOT_ROAMING };
+        final NetworkCapabilities.Builder builder = NetworkCapabilities.Builder
+                .withoutDefaultCapabilities();
+        for (int capability : netCapabilities) builder.addCapability(capability);
+        final NetworkRequest nr = new NetworkRequest.Builder()
+                .clearCapabilities()
+                .setCapabilities(builder.build())
+                .build();
+        assertArrayEquals(netCapabilities, nr.getCapabilities());
+    }
+
     @Test
     public void testBuildRequestFromExistingRequestWithBuilder() {
         assumeTrue(TestUtils.shouldTestSApis());
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
index c95dc28..c220326 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
@@ -22,7 +22,7 @@
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
 
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -35,6 +35,7 @@
 import android.content.pm.PackageManager;
 import android.net.Network;
 import android.net.TetheredClient;
+import android.net.TetheringInterface;
 import android.net.TetheringManager;
 import android.net.TetheringManager.TetheringEventCallback;
 import android.net.TetheringManager.TetheringInterfaceRegexps;
@@ -51,6 +52,7 @@
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 public final class CtsTetheringUtils {
     private TetheringManager mTm;
@@ -116,23 +118,38 @@
         }
     }
 
-    public static boolean isIfaceMatch(final List<String> ifaceRegexs, final List<String> ifaces) {
-        return isIfaceMatch(ifaceRegexs.toArray(new String[0]), ifaces);
-    }
-
-    public static boolean isIfaceMatch(final String[] ifaceRegexs, final List<String> ifaces) {
+    private static boolean isRegexMatch(final String[] ifaceRegexs, String iface) {
         if (ifaceRegexs == null) fail("ifaceRegexs should not be null");
 
+        for (String regex : ifaceRegexs) {
+            if (iface.matches(regex)) return true;
+        }
+
+        return false;
+    }
+
+    public static boolean isAnyIfaceMatch(final String[] ifaceRegexs, final List<String> ifaces) {
         if (ifaces == null) return false;
 
         for (String s : ifaces) {
-            for (String regex : ifaceRegexs) {
-                if (s.matches(regex)) {
-                    return true;
-                }
+            if (isRegexMatch(ifaceRegexs, s)) return true;
+        }
+
+        return false;
+    }
+
+    private static TetheringInterface getFirstMatchingTetheringInterface(final List<String> regexs,
+            final int type, final Set<TetheringInterface> ifaces) {
+        if (ifaces == null || regexs == null) return null;
+
+        final String[] regexArray = regexs.toArray(new String[0]);
+        for (TetheringInterface iface : ifaces) {
+            if (isRegexMatch(regexArray, iface.getInterface()) && type == iface.getType()) {
+                return iface;
             }
         }
-        return false;
+
+        return null;
     }
 
     // Must poll the callback before looking at the member.
@@ -171,6 +188,8 @@
         private TetheringInterfaceRegexps mTetherableRegex;
         private List<String> mTetherableIfaces;
         private List<String> mTetheredIfaces;
+        private String mErrorIface;
+        private int mErrorCode;
 
         @Override
         public void onTetheringSupported(boolean supported) {
@@ -191,17 +210,41 @@
         @Override
         public void onTetherableInterfacesChanged(List<String> interfaces) {
             mTetherableIfaces = interfaces;
+        }
+        // Call the interface default implementation, which will call
+        // onTetherableInterfacesChanged(List<String>). This ensures that the default implementation
+        // of the new callback method calls the old callback method and avoids the need to convert
+        // Set<TetheringInterface> to List<String> in this code.
+        @Override
+        public void onTetherableInterfacesChanged(Set<TetheringInterface> interfaces) {
+            TetheringEventCallback.super.onTetherableInterfacesChanged(interfaces);
+            assertHasAllTetheringInterfaces(interfaces, mTetherableIfaces);
             mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0));
         }
 
         @Override
         public void onTetheredInterfacesChanged(List<String> interfaces) {
             mTetheredIfaces = interfaces;
+        }
+
+        @Override
+        public void onTetheredInterfacesChanged(Set<TetheringInterface> interfaces) {
+            TetheringEventCallback.super.onTetheredInterfacesChanged(interfaces);
+            assertHasAllTetheringInterfaces(interfaces, mTetheredIfaces);
             mHistory.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0));
         }
 
         @Override
         public void onError(String ifName, int error) {
+            mErrorIface = ifName;
+            mErrorCode = error;
+        }
+
+        @Override
+        public void onError(TetheringInterface ifName, int error) {
+            TetheringEventCallback.super.onError(ifName, error);
+            assertEquals(ifName.getInterface(), mErrorIface);
+            assertEquals(error, mErrorCode);
             mHistory.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error));
         }
 
@@ -215,30 +258,66 @@
             mHistory.add(new CallbackValue(CallbackType.ON_OFFLOAD_STATUS, status, 0));
         }
 
-        public void expectTetherableInterfacesChanged(@NonNull List<String> regexs) {
+        private void assertHasAllTetheringInterfaces(Set<TetheringInterface> tetheringIfaces,
+                List<String> ifaces) {
+            // This does not check that the interfaces are the same. This checks that the
+            // List<String> has all the interface names contained by the Set<TetheringInterface>.
+            assertEquals(tetheringIfaces.size(), ifaces.size());
+            for (TetheringInterface tether : tetheringIfaces) {
+                assertTrue("iface " + tether.getInterface()
+                        + " seen by new callback but not old callback",
+                        ifaces.contains(tether.getInterface()));
+            }
+        }
+
+        public void expectTetherableInterfacesChanged(@NonNull final List<String> regexs,
+                final int type) {
             assertNotNull("No expected tetherable ifaces callback", mCurrent.poll(TIMEOUT_MS,
                 (cv) -> {
                     if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) return false;
-                    final List<String> interfaces = (List<String>) cv.callbackParam;
-                    return isIfaceMatch(regexs, interfaces);
+                    final Set<TetheringInterface> interfaces =
+                            (Set<TetheringInterface>) cv.callbackParam;
+                    return getFirstMatchingTetheringInterface(regexs, type, interfaces) != null;
                 }));
         }
 
-        public void expectTetheredInterfacesChanged(@NonNull List<String> regexs) {
-            assertNotNull("No expected tethered ifaces callback", mCurrent.poll(TIMEOUT_MS,
-                (cv) -> {
-                    if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false;
+        public void expectNoTetheringActive() {
+            assertNotNull("At least one tethering type unexpectedly active",
+                    mCurrent.poll(TIMEOUT_MS, (cv) -> {
+                        if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false;
 
-                    final List<String> interfaces = (List<String>) cv.callbackParam;
+                        return ((Set<TetheringInterface>) cv.callbackParam).isEmpty();
+                    }));
+        }
 
-                    // Null regexs means no active tethering.
-                    if (regexs == null) return interfaces.isEmpty();
+        public TetheringInterface expectTetheredInterfacesChanged(
+                @NonNull final List<String> regexs, final int type) {
+            while (true) {
+                final CallbackValue cv = mCurrent.poll(TIMEOUT_MS, c -> true);
+                if (cv == null) {
+                    fail("No expected tethered ifaces callback, expected type: " + type);
+                }
 
-                    return isIfaceMatch(regexs, interfaces);
-                }));
+                if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) continue;
+
+                final Set<TetheringInterface> interfaces =
+                        (Set<TetheringInterface>) cv.callbackParam;
+
+                final TetheringInterface iface =
+                        getFirstMatchingTetheringInterface(regexs, type, interfaces);
+
+                if (iface != null) return iface;
+            }
         }
 
         public void expectCallbackStarted() {
+            // This method uses its own readhead because it just check whether last tethering status
+            // is updated after TetheringEventCallback get registered but do not check content
+            // of received callbacks. Using shared readhead (mCurrent) only when the callbacks the
+            // method polled is also not necessary for other methods which using shared readhead.
+            // All of methods using mCurrent is order mattered.
+            final ArrayTrackRecord<CallbackValue>.ReadHead history =
+                    mHistory.newReadHead();
             int receivedBitMap = 0;
             // The each bit represent a type from CallbackType.ON_*.
             // Expect all of callbacks except for ON_ERROR.
@@ -246,7 +325,7 @@
             // Receive ON_ERROR on started callback is not matter. It just means tethering is
             // failed last time, should able to continue the test this time.
             while ((receivedBitMap & expectedBitMap) != expectedBitMap) {
-                final CallbackValue cv = mCurrent.poll(TIMEOUT_MS, c -> true);
+                final CallbackValue cv = history.poll(TIMEOUT_MS, c -> true);
                 if (cv == null) {
                     fail("No expected callbacks, " + "expected bitmap: "
                             + expectedBitMap + ", actual: " + receivedBitMap);
@@ -269,14 +348,14 @@
             }));
         }
 
-        public void expectErrorOrTethered(final String iface) {
+        public void expectErrorOrTethered(final TetheringInterface iface) {
             assertNotNull("No expected callback", mCurrent.poll(TIMEOUT_MS, (cv) -> {
                 if (cv.callbackType == CallbackType.ON_ERROR
-                        && iface.equals((String) cv.callbackParam)) {
+                        && iface.equals((TetheringInterface) cv.callbackParam)) {
                     return true;
                 }
                 if (cv.callbackType == CallbackType.ON_TETHERED_IFACES
-                        && ((List<String>) cv.callbackParam).contains(iface)) {
+                        && ((Set<TetheringInterface>) cv.callbackParam).contains(iface)) {
                     return true;
                 }
 
@@ -309,33 +388,12 @@
             assumeTetheringSupported();
 
             assumeTrue(!getTetheringInterfaceRegexps().getTetherableWifiRegexs().isEmpty());
-
-            final PackageManager pm = ctx.getPackageManager();
-            assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_WIFI));
-
-            WifiManager wm = ctx.getSystemService(WifiManager.class);
-            // Wifi feature flags only work when wifi is on.
-            final boolean previousWifiEnabledState = wm.isWifiEnabled();
-            try {
-                if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi enable");
-                waitForWifiEnabled(ctx);
-                assumeTrue(wm.isPortableHotspotSupported());
-            } finally {
-                if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi disable");
-            }
+            assumeTrue(isPortableHotspotSupported(ctx));
         }
 
         public TetheringInterfaceRegexps getTetheringInterfaceRegexps() {
             return mTetherableRegex;
         }
-
-        public List<String> getTetherableInterfaces() {
-            return mTetherableIfaces;
-        }
-
-        public List<String> getTetheredInterfaces() {
-            return mTetheredIfaces;
-        }
     }
 
     private static void waitForWifiEnabled(final Context ctx) throws Exception {
@@ -382,14 +440,31 @@
         return callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
     }
 
-    public static boolean isWifiTetheringSupported(final TestTetheringEventCallback callback) {
-        return !getWifiTetherableInterfaceRegexps(callback).isEmpty();
+    public static boolean isWifiTetheringSupported(final Context ctx,
+            final TestTetheringEventCallback callback) throws Exception {
+        return !getWifiTetherableInterfaceRegexps(callback).isEmpty()
+                && isPortableHotspotSupported(ctx);
     }
 
-    public void startWifiTethering(final TestTetheringEventCallback callback)
+    /* Returns if wifi supports hotspot. */
+    private static boolean isPortableHotspotSupported(final Context ctx) throws Exception {
+        final PackageManager pm = ctx.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_WIFI)) return false;
+        final WifiManager wm = ctx.getSystemService(WifiManager.class);
+        // Wifi feature flags only work when wifi is on.
+        final boolean previousWifiEnabledState = wm.isWifiEnabled();
+        try {
+            if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi enable");
+            waitForWifiEnabled(ctx);
+            return wm.isPortableHotspotSupported();
+        } finally {
+            if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi disable");
+        }
+    }
+
+    public TetheringInterface startWifiTethering(final TestTetheringEventCallback callback)
             throws InterruptedException {
         final List<String> wifiRegexs = getWifiTetherableInterfaceRegexps(callback);
-        assertFalse(isIfaceMatch(wifiRegexs, callback.getTetheredInterfaces()));
 
         final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
         final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
@@ -397,11 +472,14 @@
         mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
         startTetheringCallback.verifyTetheringStarted();
 
-        callback.expectTetheredInterfacesChanged(wifiRegexs);
+        final TetheringInterface iface =
+                callback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI);
 
         callback.expectOneOfOffloadStatusChanged(
                 TETHER_HARDWARE_OFFLOAD_STARTED,
                 TETHER_HARDWARE_OFFLOAD_FAILED);
+
+        return iface;
     }
 
     private static class StopSoftApCallback implements SoftApCallback {
@@ -441,7 +519,7 @@
     public void stopWifiTethering(final TestTetheringEventCallback callback) {
         mTm.stopTethering(TETHERING_WIFI);
         expectSoftApDisabled();
-        callback.expectTetheredInterfacesChanged(null);
+        callback.expectNoTetheringActive();
         callback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
     }
 }
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 71a81ff..0a5e506 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -26,8 +26,7 @@
 import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
 import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
-import static android.net.cts.util.CtsTetheringUtils.isIfaceMatch;
-import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
+import static android.net.cts.util.CtsTetheringUtils.isAnyIfaceMatch;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -48,6 +47,7 @@
 import android.net.LinkAddress;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.TetheringInterface;
 import android.net.TetheringManager;
 import android.net.TetheringManager.OnTetheringEntitlementResultListener;
 import android.net.TetheringManager.TetheringInterfaceRegexps;
@@ -190,13 +190,13 @@
         }
 
         private boolean isIfaceActive(final String[] ifaceRegexs, final TetherState state) {
-            return isIfaceMatch(ifaceRegexs, state.mActive);
+            return isAnyIfaceMatch(ifaceRegexs, state.mActive);
         }
 
         private void assertNoErroredIfaces(final TetherState state, final String[] ifaceRegexs) {
             if (state == null || state.mErrored == null) return;
 
-            if (isIfaceMatch(ifaceRegexs, state.mErrored)) {
+            if (isAnyIfaceMatch(ifaceRegexs, state.mErrored)) {
                 fail("Found failed tethering interfaces: " + Arrays.toString(state.mErrored.toArray()));
             }
         }
@@ -256,12 +256,13 @@
 
         try {
             tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            tetherEventCallback.expectNoTetheringActive();
 
-            mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
+            final TetheringInterface tetheredIface =
+                    mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
 
-            final List<String> tetheredIfaces = tetherEventCallback.getTetheredInterfaces();
-            assertEquals(1, tetheredIfaces.size());
-            final String wifiTetheringIface = tetheredIfaces.get(0);
+            assertNotNull(tetheredIface);
+            final String wifiTetheringIface = tetheredIface.getInterface();
 
             mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
 
@@ -272,7 +273,8 @@
                 if (ret == TETHER_ERROR_NO_ERROR) {
                     // If calling #tether successful, there is a callback to tell the result of
                     // tethering setup.
-                    tetherEventCallback.expectErrorOrTethered(wifiTetheringIface);
+                    tetherEventCallback.expectErrorOrTethered(
+                            new TetheringInterface(TETHERING_WIFI, wifiTetheringIface));
                 }
             } finally {
                 mTM.untether(wifiTetheringIface);
@@ -319,7 +321,7 @@
             mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
 
             mTM.stopAllTethering();
-            tetherEventCallback.expectTetheredInterfacesChanged(null);
+            tetherEventCallback.expectNoTetheringActive();
         } finally {
             mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
         }
@@ -417,6 +419,7 @@
 
         try {
             tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            tetherEventCallback.expectNoTetheringActive();
 
             previousWifiEnabledState = mWm.isWifiEnabled();
             if (previousWifiEnabledState) {
diff --git a/tests/integration/src/android/net/TestNetworkStackClient.kt b/tests/integration/src/android/net/TestNetworkStackClient.kt
index 01eb514..61ef5bd 100644
--- a/tests/integration/src/android/net/TestNetworkStackClient.kt
+++ b/tests/integration/src/android/net/TestNetworkStackClient.kt
@@ -19,6 +19,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.net.networkstack.NetworkStackClientBase
 import android.os.IBinder
 import com.android.server.net.integrationtests.TestNetworkStackService
 import org.mockito.Mockito.any
@@ -29,28 +30,22 @@
 
 const val TEST_ACTION_SUFFIX = ".Test"
 
-class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) {
+class TestNetworkStackClient(private val context: Context) : NetworkStackClientBase() {
     // TODO: consider switching to TrackRecord for more expressive checks
     private val lastCallbacks = HashMap<Network, INetworkMonitorCallbacks>()
+    private val moduleConnector = ConnectivityModuleConnector { _, action, _, _ ->
+        val intent = Intent(action)
+        val serviceName = TestNetworkStackService::class.qualifiedName
+                ?: fail("TestNetworkStackService name not found")
+        intent.component = ComponentName(context.packageName, serviceName)
+        return@ConnectivityModuleConnector intent
+    }.also { it.init(context) }
 
-    private class TestDependencies(private val context: Context) : Dependencies {
-        override fun addToServiceManager(service: IBinder) = Unit
-        override fun checkCallerUid() = Unit
-
-        override fun getConnectivityModuleConnector(): ConnectivityModuleConnector {
-            return ConnectivityModuleConnector { _, _, _, inSystemProcess ->
-                getNetworkStackIntent(inSystemProcess)
-            }.also { it.init(context) }
-        }
-
-        private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? {
-            // Simulate out-of-system-process config: in-process service not found (null intent)
-            if (inSystemProcess) return null
-            val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX)
-            val serviceName = TestNetworkStackService::class.qualifiedName
-                    ?: fail("TestNetworkStackService name not found")
-            intent.component = ComponentName(context.packageName, serviceName)
-            return intent
+    fun start() {
+        moduleConnector.startModuleService(
+                INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX,
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) { connector ->
+            onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(connector))
         }
     }
 
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index b6e4274..e039ef0 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -157,7 +157,6 @@
         doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString())
 
         networkStackClient = TestNetworkStackClient(realContext)
-        networkStackClient.init()
         networkStackClient.start()
 
         service = TestConnectivityService(makeDependencies())
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index e809550..17db179 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -41,6 +41,7 @@
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
+import android.net.NetworkScore;
 import android.net.NetworkSpecifier;
 import android.net.QosFilter;
 import android.net.SocketKeepalive;
@@ -51,7 +52,6 @@
 import android.util.Range;
 
 import com.android.net.module.util.ArrayTrackRecord;
-import com.android.server.connectivity.ConnectivityConstants;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.TestableNetworkCallback;
 
@@ -69,7 +69,7 @@
     private final ConditionVariable mDisconnected = new ConditionVariable();
     private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
     private final AtomicBoolean mConnected = new AtomicBoolean(false);
-    private int mScore;
+    private NetworkScore mScore;
     private NetworkAgent mNetworkAgent;
     private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED;
     private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
@@ -90,23 +90,23 @@
         mNetworkCapabilities.addTransportType(transport);
         switch (transport) {
             case TRANSPORT_ETHERNET:
-                mScore = 70;
+                mScore = new NetworkScore.Builder().setLegacyInt(70).build();
                 break;
             case TRANSPORT_WIFI:
-                mScore = 60;
+                mScore = new NetworkScore.Builder().setLegacyInt(60).build();
                 break;
             case TRANSPORT_CELLULAR:
-                mScore = 50;
+                mScore = new NetworkScore.Builder().setLegacyInt(50).build();
                 break;
             case TRANSPORT_WIFI_AWARE:
-                mScore = 20;
+                mScore = new NetworkScore.Builder().setLegacyInt(20).build();
                 break;
             case TRANSPORT_VPN:
                 mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
                 // VPNs deduce the SUSPENDED capability from their underlying networks and there
                 // is no public API to let VPN services set it.
                 mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
-                mScore = ConnectivityConstants.VPN_DEFAULT_SCORE;
+                mScore = new NetworkScore.Builder().setLegacyInt(101).build();
                 break;
             default:
                 throw new UnsupportedOperationException("unimplemented network type");
@@ -199,12 +199,23 @@
         }
     }
 
+    public void setScore(@NonNull final NetworkScore score) {
+        mScore = score;
+        mNetworkAgent.sendNetworkScore(score);
+    }
+
     public void adjustScore(int change) {
-        mScore += change;
+        final int newLegacyScore = mScore.getLegacyInt() + change;
+        final NetworkScore.Builder builder = new NetworkScore.Builder()
+                .setLegacyInt(newLegacyScore);
+        if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI) && newLegacyScore < 50) {
+            builder.setExiting(true);
+        }
+        mScore = builder.build();
         mNetworkAgent.sendNetworkScore(mScore);
     }
 
-    public int getScore() {
+    public NetworkScore getScore() {
         return mScore;
     }
 
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index fcf3754..7d6f95f 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -104,6 +104,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+import static android.net.NetworkScore.KEEP_CONNECTED_FOR_HANDOVER;
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
@@ -228,7 +229,6 @@
 import android.net.NetworkScore;
 import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
-import android.net.NetworkStackClient;
 import android.net.NetworkStateSnapshot;
 import android.net.NetworkTestResultParcelable;
 import android.net.OemNetworkPreferences;
@@ -248,6 +248,7 @@
 import android.net.VpnManager;
 import android.net.VpnTransportInfo;
 import android.net.metrics.IpConnectivityLog;
+import android.net.networkstack.NetworkStackClientBase;
 import android.net.resolv.aidl.Nat64PrefixEventParcel;
 import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
 import android.net.shared.NetworkMonitorUtils;
@@ -299,8 +300,9 @@
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.net.module.util.ArrayTrackRecord;
+import com.android.net.module.util.CollectionUtils;
 import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
-import com.android.server.connectivity.ConnectivityConstants;
+import com.android.server.ConnectivityService.NetworkRequestInfo;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.NetworkAgentInfo;
@@ -415,6 +417,7 @@
     private static final String VPN_IFNAME = "tun10042";
     private static final String TEST_PACKAGE_NAME = "com.android.test.package";
     private static final int TEST_PACKAGE_UID = 123;
+    private static final int TEST_PACKAGE_UID2 = 321;
     private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
 
     private static final String INTERFACE_NAME = "interface";
@@ -461,7 +464,7 @@
     @Mock NetworkStatsManager mStatsManager;
     @Mock IDnsResolver mMockDnsResolver;
     @Mock INetd mMockNetd;
-    @Mock NetworkStackClient mNetworkStack;
+    @Mock NetworkStackClientBase mNetworkStack;
     @Mock PackageManager mPackageManager;
     @Mock UserManager mUserManager;
     @Mock NotificationManager mNotificationManager;
@@ -1026,8 +1029,6 @@
      * operations have been processed and test for them.
      */
     private static class MockNetworkFactory extends NetworkFactory {
-        private final ConditionVariable mNetworkStartedCV = new ConditionVariable();
-        private final ConditionVariable mNetworkStoppedCV = new ConditionVariable();
         private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
 
         static class RequestEntry {
@@ -1039,11 +1040,8 @@
             }
 
             static final class Add extends RequestEntry {
-                public final int factorySerialNumber;
-
-                Add(@NonNull final NetworkRequest request, final int factorySerialNumber) {
+                Add(@NonNull final NetworkRequest request) {
                     super(request);
-                    this.factorySerialNumber = factorySerialNumber;
                 }
             }
 
@@ -1052,6 +1050,11 @@
                     super(request);
                 }
             }
+
+            @Override
+            public String toString() {
+                return "RequestEntry [ " + getClass().getName() + " : " + request + " ]";
+            }
         }
 
         // History of received requests adds and removes.
@@ -1063,7 +1066,6 @@
             return obj;
         }
 
-
         public RequestEntry.Add expectRequestAdd() {
             return failIfNull((RequestEntry.Add) mRequestHistory.poll(TIMEOUT_MS,
                     it -> it instanceof RequestEntry.Add), "Expected request add");
@@ -1103,40 +1105,28 @@
 
         protected void startNetwork() {
             mNetworkStarted.set(true);
-            mNetworkStartedCV.open();
         }
 
         protected void stopNetwork() {
             mNetworkStarted.set(false);
-            mNetworkStoppedCV.open();
         }
 
         public boolean getMyStartRequested() {
             return mNetworkStarted.get();
         }
 
-        public ConditionVariable getNetworkStartedCV() {
-            mNetworkStartedCV.close();
-            return mNetworkStartedCV;
-        }
-
-        public ConditionVariable getNetworkStoppedCV() {
-            mNetworkStoppedCV.close();
-            return mNetworkStoppedCV;
-        }
 
         @Override
-        protected void handleAddRequest(NetworkRequest request, int score,
-                int factorySerialNumber) {
+        protected void needNetworkFor(NetworkRequest request) {
             mNetworkRequests.put(request.requestId, request);
-            super.handleAddRequest(request, score, factorySerialNumber);
-            mRequestHistory.add(new RequestEntry.Add(request, factorySerialNumber));
+            super.needNetworkFor(request);
+            mRequestHistory.add(new RequestEntry.Add(request));
         }
 
         @Override
-        protected void handleRemoveRequest(NetworkRequest request) {
+        protected void releaseNetworkFor(NetworkRequest request) {
             mNetworkRequests.remove(request.requestId);
-            super.handleRemoveRequest(request);
+            super.releaseNetworkFor(request);
             mRequestHistory.add(new RequestEntry.Remove(request));
         }
 
@@ -1170,6 +1160,10 @@
         return ranges;
     }
 
+    private Set<UidRange> uidRangesForUids(Collection<Integer> uids) {
+        return uidRangesForUids(CollectionUtils.toIntArray(uids));
+    }
+
     private static Looper startHandlerThreadAndReturnLooper() {
         final HandlerThread handlerThread = new HandlerThread("MockVpnThread");
         handlerThread.start();
@@ -1536,6 +1530,8 @@
     private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER, "",
             UserInfo.FLAG_PRIMARY);
     private static final UserHandle PRIMARY_USER_HANDLE = new UserHandle(PRIMARY_USER);
+    private static final int SECONDARY_USER = 10;
+    private static final UserHandle SECONDARY_USER_HANDLE = new UserHandle(SECONDARY_USER);
 
     private static final int RESTRICTED_USER = 1;
     private static final UserInfo RESTRICTED_USER_INFO = new UserInfo(RESTRICTED_USER, "",
@@ -3077,8 +3073,9 @@
         }
 
         NetworkCapabilities filter = new NetworkCapabilities();
+        filter.addTransportType(TRANSPORT_CELLULAR);
         filter.addCapability(capability);
-        // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add
+        // Add NOT_VCN_MANAGED capability into filter unconditionally since some requests will add
         // NOT_VCN_MANAGED automatically but not for NetworkCapabilities,
         // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details.
         filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
@@ -3086,47 +3083,67 @@
         handlerThread.start();
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
                 mServiceContext, "testFactory", filter, mCsHandlerThread);
-        testFactory.setScoreFilter(40);
-        ConditionVariable cv = testFactory.getNetworkStartedCV();
+        testFactory.setScoreFilter(45);
         testFactory.register();
-        testFactory.expectRequestAdd();
-        testFactory.assertRequestCountEquals(1);
-        int expectedRequestCount = 1;
-        NetworkCallback networkCallback = null;
-        // For non-INTERNET capabilities we cannot rely on the default request being present, so
-        // add one.
+
+        final NetworkCallback networkCallback;
         if (capability != NET_CAPABILITY_INTERNET) {
+            // If the capability passed in argument is part of the default request, then the
+            // factory will see the default request. Otherwise the filter will prevent the
+            // factory from seeing it. In that case, add a request so it can be tested.
             assertFalse(testFactory.getMyStartRequested());
             NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build();
             networkCallback = new NetworkCallback();
             mCm.requestNetwork(request, networkCallback);
-            expectedRequestCount++;
-            testFactory.expectRequestAdd();
+        } else {
+            networkCallback = null;
         }
-        waitFor(cv);
-        testFactory.assertRequestCountEquals(expectedRequestCount);
+        testFactory.expectRequestAdd();
+        testFactory.assertRequestCountEquals(1);
         assertTrue(testFactory.getMyStartRequested());
 
         // Now bring in a higher scored network.
         TestNetworkAgentWrapper testAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        // Rather than create a validated network which complicates things by registering it's
-        // own NetworkRequest during startup, just bump up the score to cancel out the
-        // unvalidated penalty.
-        testAgent.adjustScore(40);
-        cv = testFactory.getNetworkStoppedCV();
-
-        // When testAgent connects, ConnectivityService will re-send us all current requests with
-        // the new score. There are expectedRequestCount such requests, and we must wait for all of
-        // them.
-        testAgent.connect(false);
+        // When testAgent connects, because of its score (50 legacy int / cell transport)
+        // it will beat or equal the testFactory's offer, so the request will be removed.
+        // Note the agent as validated only if the capability is INTERNET, as it's the only case
+        // where it makes sense.
+        testAgent.connect(NET_CAPABILITY_INTERNET == capability /* validated */);
         testAgent.addCapability(capability);
-        waitFor(cv);
-        testFactory.expectRequestAdds(expectedRequestCount);
-        testFactory.assertRequestCountEquals(expectedRequestCount);
+        testFactory.expectRequestRemove();
+        testFactory.assertRequestCountEquals(0);
         assertFalse(testFactory.getMyStartRequested());
 
+        // Add a request and make sure it's not sent to the factory, because the agent
+        // is satisfying it better.
+        final NetworkCallback cb = new ConnectivityManager.NetworkCallback();
+        mCm.requestNetwork(new NetworkRequest.Builder().addCapability(capability).build(), cb);
+        expectNoRequestChanged(testFactory);
+        testFactory.assertRequestCountEquals(0);
+        assertFalse(testFactory.getMyStartRequested());
+
+        // If using legacy scores, make the test agent weak enough to have the exact same score as
+        // the factory (50 for cell - 5 adjustment). Make sure the factory doesn't see the request.
+        // If not using legacy score, this is a no-op and the "same score removes request" behavior
+        // has already been tested above.
+        testAgent.adjustScore(-5);
+        expectNoRequestChanged(testFactory);
+        assertFalse(testFactory.getMyStartRequested());
+
+        // Make the test agent weak enough that the factory will see the two requests (the one that
+        // was just sent, and either the default one or the one sent at the top of this test if
+        // the default won't be seen).
+        testAgent.setScore(new NetworkScore.Builder().setLegacyInt(2).setExiting(true).build());
+        testFactory.expectRequestAdds(2);
+        testFactory.assertRequestCountEquals(2);
+        assertTrue(testFactory.getMyStartRequested());
+
+        // Now unregister and make sure the request is removed.
+        mCm.unregisterNetworkCallback(cb);
+        testFactory.expectRequestRemove();
+
         // Bring in a bunch of requests.
-        assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+        assertEquals(1, testFactory.getMyRequestCount());
         ConnectivityManager.NetworkCallback[] networkCallbacks =
                 new ConnectivityManager.NetworkCallback[10];
         for (int i = 0; i< networkCallbacks.length; i++) {
@@ -3136,24 +3153,28 @@
             mCm.requestNetwork(builder.build(), networkCallbacks[i]);
         }
         testFactory.expectRequestAdds(10);
-        testFactory.assertRequestCountEquals(10 + expectedRequestCount);
-        assertFalse(testFactory.getMyStartRequested());
+        testFactory.assertRequestCountEquals(11); // +1 for the default/test specific request
+        assertTrue(testFactory.getMyStartRequested());
 
         // Remove the requests.
         for (int i = 0; i < networkCallbacks.length; i++) {
             mCm.unregisterNetworkCallback(networkCallbacks[i]);
         }
         testFactory.expectRequestRemoves(10);
-        testFactory.assertRequestCountEquals(expectedRequestCount);
+        testFactory.assertRequestCountEquals(1);
+        assertTrue(testFactory.getMyStartRequested());
+
+        // Adjust the agent score up again. Expect the request to be withdrawn.
+        testAgent.setScore(new NetworkScore.Builder().setLegacyInt(50).build());
+        testFactory.expectRequestRemove();
+        testFactory.assertRequestCountEquals(0);
         assertFalse(testFactory.getMyStartRequested());
 
         // Drop the higher scored network.
-        cv = testFactory.getNetworkStartedCV();
         testAgent.disconnect();
-        waitFor(cv);
-        testFactory.expectRequestAdds(expectedRequestCount);
-        testFactory.assertRequestCountEquals(expectedRequestCount);
-        assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+        testFactory.expectRequestAdd();
+        testFactory.assertRequestCountEquals(1);
+        assertEquals(1, testFactory.getMyRequestCount());
         assertTrue(testFactory.getMyStartRequested());
 
         testFactory.terminate();
@@ -3184,10 +3205,49 @@
         // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed.
     }
 
+    @Ignore("Refactoring in progress b/184028345")
+    @Test
+    public void testRegisterIgnoringScore() throws Exception {
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(90).build());
+        mWiFiNetworkAgent.connect(true /* validated */);
+
+        // Make sure the factory sees the default network
+        final NetworkCapabilities filter = new NetworkCapabilities();
+        filter.addTransportType(TRANSPORT_CELLULAR);
+        filter.addCapability(NET_CAPABILITY_INTERNET);
+        filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
+        handlerThread.start();
+        final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+                mServiceContext, "testFactory", filter, mCsHandlerThread);
+        testFactory.register();
+
+        final MockNetworkFactory testFactoryAll = new MockNetworkFactory(handlerThread.getLooper(),
+                mServiceContext, "testFactoryAll", filter, mCsHandlerThread);
+        testFactoryAll.registerIgnoringScore();
+
+        // The regular test factory should not see the request, because WiFi is stronger than cell.
+        expectNoRequestChanged(testFactory);
+        // With ignoringScore though the request is seen.
+        testFactoryAll.expectRequestAdd();
+
+        // The legacy int will be ignored anyway, set the only other knob to true
+        mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(110)
+                .setTransportPrimary(true).build());
+
+        expectNoRequestChanged(testFactory); // still not seeing the request
+        expectNoRequestChanged(testFactoryAll); // still seeing the request
+
+        mWiFiNetworkAgent.disconnect();
+    }
+
     @Test
     public void testNetworkFactoryUnregister() throws Exception {
+        // Make sure the factory sees the default network
         final NetworkCapabilities filter = new NetworkCapabilities();
-        filter.clearAll();
+        filter.addCapability(NET_CAPABILITY_INTERNET);
+        filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
 
         final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
         handlerThread.start();
@@ -4446,6 +4506,7 @@
         testFactory.register();
 
         try {
+            // Expect the factory to receive the default network request.
             testFactory.expectRequestAdd();
             testFactory.assertRequestCountEquals(1);
             assertTrue(testFactory.getMyStartRequested());
@@ -4454,25 +4515,44 @@
             mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
             // Score 60 - 40 penalty for not validated yet, then 60 when it validates
             mWiFiNetworkAgent.connect(true);
-            // Default request and mobile always on request
-            testFactory.expectRequestAdds(2);
+            // The network connects with a low score, so the offer can still beat it and
+            // nothing happens. Then the network validates, and the offer with its filter score
+            // of 40 can no longer beat it and the request is removed.
+            testFactory.expectRequestRemove();
+            testFactory.assertRequestCountEquals(0);
+
             assertFalse(testFactory.getMyStartRequested());
 
-            // Turn on mobile data always on. The factory starts looking again.
+            // Turn on mobile data always on. This request will not match the wifi request, so
+            // it will be sent to the test factory whose filters allow to see it.
             setAlwaysOnNetworks(true);
             testFactory.expectRequestAdd();
-            testFactory.assertRequestCountEquals(2);
+            testFactory.assertRequestCountEquals(1);
 
             assertTrue(testFactory.getMyStartRequested());
 
             // Bring up cell data and check that the factory stops looking.
             assertLength(1, mCm.getAllNetworks());
             mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-            mCellNetworkAgent.connect(true);
-            cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-            testFactory.expectRequestAdds(2); // Unvalidated and validated
-            testFactory.assertRequestCountEquals(2);
-            // The cell network outscores the factory filter, so start is not requested.
+            mCellNetworkAgent.connect(false);
+            cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent, false, false, false,
+                    TEST_CALLBACK_TIMEOUT_MS);
+            // When cell connects, it will satisfy the "mobile always on request" right away
+            // by virtue of being the only network that can satisfy the request. However, its
+            // score is low (50 - 40 = 10) so the test factory can still hope to beat it.
+            expectNoRequestChanged(testFactory);
+
+            // Next, cell validates. This gives it a score of 50 and the test factory can't
+            // hope to beat that according to its filters. It will see the message that its
+            // offer is now unnecessary.
+            mCellNetworkAgent.setNetworkValid(true);
+            // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
+            // validated – see testPartialConnectivity.
+            mCm.reportNetworkConnectivity(mCellNetworkAgent.getNetwork(), true);
+            cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent);
+            testFactory.expectRequestRemove();
+            testFactory.assertRequestCountEquals(0);
+            // Accordingly, the factory shouldn't be started.
             assertFalse(testFactory.getMyStartRequested());
 
             // Check that cell data stays up.
@@ -4480,10 +4560,27 @@
             verifyActiveNetwork(TRANSPORT_WIFI);
             assertLength(2, mCm.getAllNetworks());
 
-            // Turn off mobile data always on and expect the request to disappear...
-            setAlwaysOnNetworks(false);
-            testFactory.expectRequestRemove();
+            // Cell disconnects. There is still the "mobile data always on" request outstanding,
+            // and the test factory should see it now that it isn't hopelessly outscored.
+            mCellNetworkAgent.disconnect();
+            cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+            assertLength(1, mCm.getAllNetworks());
+            testFactory.expectRequestAdd();
+            testFactory.assertRequestCountEquals(1);
 
+            // Reconnect cell validated, see the request disappear again. Then withdraw the
+            // mobile always on request. This will tear down cell, and there shouldn't be a
+            // blip where the test factory briefly sees the request or anything.
+            mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+            mCellNetworkAgent.connect(true);
+            cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+            assertLength(2, mCm.getAllNetworks());
+            testFactory.expectRequestRemove();
+            testFactory.assertRequestCountEquals(0);
+            setAlwaysOnNetworks(false);
+            expectNoRequestChanged(testFactory);
+            testFactory.assertRequestCountEquals(0);
+            assertFalse(testFactory.getMyStartRequested());
             // ...  and cell data to be torn down after nascent network timeout.
             cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent,
                     mService.mNascentDelayMs + TEST_CALLBACK_TIMEOUT_MS);
@@ -4786,7 +4883,8 @@
         handlerThread.start();
         NetworkCapabilities filter = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_WIFI)
-                .addCapability(NET_CAPABILITY_INTERNET);
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
                 mServiceContext, "testFactory", filter, mCsHandlerThread);
         testFactory.setScoreFilter(40);
@@ -4795,32 +4893,43 @@
         testFactory.register();
         testFactory.expectRequestAdd();
 
-        // Now file the test request and expect it.
-        mCm.requestNetwork(nr, networkCallback);
-        final NetworkRequest newRequest = testFactory.expectRequestAdd().request;
+        try {
+            // Now file the test request and expect it.
+            mCm.requestNetwork(nr, networkCallback);
+            final NetworkRequest newRequest = testFactory.expectRequestAdd().request;
 
-        if (preUnregister) {
-            mCm.unregisterNetworkCallback(networkCallback);
+            if (preUnregister) {
+                mCm.unregisterNetworkCallback(networkCallback);
 
-            // Simulate the factory releasing the request as unfulfillable: no-op since
-            // the callback has already been unregistered (but a test that no exceptions are
-            // thrown).
-            testFactory.triggerUnfulfillable(newRequest);
-        } else {
-            // Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
-            testFactory.triggerUnfulfillable(newRequest);
+                // The request has been released : the factory should see it removed
+                // immediately.
+                testFactory.expectRequestRemove();
 
-            networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
+                // Simulate the factory releasing the request as unfulfillable: no-op since
+                // the callback has already been unregistered (but a test that no exceptions are
+                // thrown).
+                testFactory.triggerUnfulfillable(newRequest);
+            } else {
+                // Simulate the factory releasing the request as unfulfillable and expect
+                // onUnavailable!
+                testFactory.triggerUnfulfillable(newRequest);
 
-            // unregister network callback - a no-op (since already freed by the
-            // on-unavailable), but should not fail or throw exceptions.
-            mCm.unregisterNetworkCallback(networkCallback);
+                networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
+
+                // Declaring a request unfulfillable releases it automatically.
+                testFactory.expectRequestRemove();
+
+                // unregister network callback - a no-op (since already freed by the
+                // on-unavailable), but should not fail or throw exceptions.
+                mCm.unregisterNetworkCallback(networkCallback);
+
+                // The factory should not see any further removal, as this request has
+                // already been removed.
+            }
+        } finally {
+            testFactory.terminate();
+            handlerThread.quit();
         }
-
-        testFactory.expectRequestRemove();
-
-        testFactory.terminate();
-        handlerThread.quit();
     }
 
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
@@ -5977,7 +6086,8 @@
         // called again, it does. For example, connect Ethernet, but with a low score, such that it
         // does not become the default network.
         mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
-        mEthernetNetworkAgent.adjustScore(-40);
+        mEthernetNetworkAgent.setScore(
+                new NetworkScore.Builder().setLegacyInt(30).setExiting(true).build());
         mEthernetNetworkAgent.connect(false);
         waitForIdle();
         verify(mStatsManager).notifyNetworkStatus(any(List.class),
@@ -6852,8 +6962,6 @@
         callback.expectAvailableCallbacksUnvalidated(mMockVpn);
         callback.assertNoCallback();
 
-        assertTrue(mMockVpn.getAgent().getScore() > mEthernetNetworkAgent.getScore());
-        assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, mMockVpn.getAgent().getScore());
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
 
         NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
@@ -10083,7 +10191,7 @@
         mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
         waitForIdle();
 
-        final ConnectivityService.NetworkRequestInfo[] nriOutput = mService.requestsSortedById();
+        final NetworkRequestInfo[] nriOutput = mService.requestsSortedById();
 
         assertTrue(nriOutput.length > 1);
         for (int i = 0; i < nriOutput.length - 1; i++) {
@@ -10162,6 +10270,83 @@
         }
     }
 
+    @Test
+    public void testKeepConnected() throws Exception {
+        setAlwaysOnNetworks(false);
+        registerDefaultNetworkCallbacks();
+        final TestNetworkCallback allNetworksCb = new TestNetworkCallback();
+        final NetworkRequest allNetworksRequest = new NetworkRequest.Builder().clearCapabilities()
+                .build();
+        mCm.registerNetworkCallback(allNetworksRequest, allNetworksCb);
+
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true /* validated */);
+
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true /* validated */);
+
+        mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        // While the default callback doesn't see the network before it's validated, the listen
+        // sees the network come up and validate later
+        allNetworksCb.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        allNetworksCb.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
+        allNetworksCb.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent,
+                TEST_LINGER_DELAY_MS * 2);
+
+        // The cell network has disconnected (see LOST above) because it was outscored and
+        // had no requests (see setAlwaysOnNetworks(false) above)
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        final NetworkScore score = new NetworkScore.Builder().setLegacyInt(30).build();
+        mCellNetworkAgent.setScore(score);
+        mCellNetworkAgent.connect(false /* validated */);
+
+        // The cell network gets torn down right away.
+        allNetworksCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent,
+                TEST_NASCENT_DELAY_MS * 2);
+        allNetworksCb.assertNoCallback();
+
+        // Now create a cell network with KEEP_CONNECTED_FOR_HANDOVER and make sure it's
+        // not disconnected immediately when outscored.
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        final NetworkScore scoreKeepup = new NetworkScore.Builder().setLegacyInt(30)
+                .setKeepConnectedReason(KEEP_CONNECTED_FOR_HANDOVER).build();
+        mCellNetworkAgent.setScore(scoreKeepup);
+        mCellNetworkAgent.connect(true /* validated */);
+
+        allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mDefaultNetworkCallback.assertNoCallback();
+
+        mWiFiNetworkAgent.disconnect();
+
+        allNetworksCb.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+        // Reconnect a WiFi network and make sure the cell network is still not torn down.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true /* validated */);
+
+        allNetworksCb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+
+        // Now remove the reason to keep connected and make sure the network lingers and is
+        // torn down.
+        mCellNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).build());
+        allNetworksCb.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent,
+                TEST_NASCENT_DELAY_MS * 2);
+        allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent,
+                TEST_LINGER_DELAY_MS * 2);
+        mDefaultNetworkCallback.assertNoCallback();
+
+        mCm.unregisterNetworkCallback(allNetworksCb);
+        // mDefaultNetworkCallback will be unregistered by tearDown()
+    }
+
     private class QosCallbackMockHelper {
         @NonNull public final QosFilter mFilter;
         @NonNull public final IQosCallback mCallback;
@@ -10366,8 +10551,7 @@
                 .thenReturn(hasFeature);
     }
 
-    private Range<Integer> getNriFirstUidRange(
-            @NonNull final ConnectivityService.NetworkRequestInfo nri) {
+    private Range<Integer> getNriFirstUidRange(@NonNull final NetworkRequestInfo nri) {
         return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next();
     }
 
@@ -10406,7 +10590,7 @@
                 OEM_NETWORK_PREFERENCE_OEM_PAID;
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+        final ArraySet<NetworkRequestInfo> nris =
                 mService.new OemNetworkRequestFactory()
                         .createNrisFromOemNetworkPreferences(
                                 createDefaultOemNetworkPreferences(prefToTest));
@@ -10435,7 +10619,7 @@
                 OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+        final ArraySet<NetworkRequestInfo> nris =
                 mService.new OemNetworkRequestFactory()
                         .createNrisFromOemNetworkPreferences(
                                 createDefaultOemNetworkPreferences(prefToTest));
@@ -10461,7 +10645,7 @@
                 OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+        final ArraySet<NetworkRequestInfo> nris =
                 mService.new OemNetworkRequestFactory()
                         .createNrisFromOemNetworkPreferences(
                                 createDefaultOemNetworkPreferences(prefToTest));
@@ -10484,7 +10668,7 @@
                 OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+        final ArraySet<NetworkRequestInfo> nris =
                 mService.new OemNetworkRequestFactory()
                         .createNrisFromOemNetworkPreferences(
                                 createDefaultOemNetworkPreferences(prefToTest));
@@ -10517,7 +10701,7 @@
                 .build();
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+        final ArraySet<NetworkRequestInfo> nris =
                 mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
 
         assertNotNull(nris);
@@ -10542,7 +10726,7 @@
                 .build();
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final List<ConnectivityService.NetworkRequestInfo> nris =
+        final List<NetworkRequestInfo> nris =
                 new ArrayList<>(
                         mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
                                 pref));
@@ -10574,7 +10758,7 @@
                 .build();
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final List<ConnectivityService.NetworkRequestInfo> nris =
+        final List<NetworkRequestInfo> nris =
                 new ArrayList<>(
                         mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
                                 pref));
@@ -10616,7 +10800,7 @@
                 .build();
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+        final ArraySet<NetworkRequestInfo> nris =
                 mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
 
         assertEquals(expectedNumOfNris, nris.size());
@@ -10709,8 +10893,7 @@
         // each time to confirm it doesn't change under test.
         final int expectedDefaultNetworkRequestsSize = 2;
         assertEquals(expectedDefaultNetworkRequestsSize, mService.mDefaultNetworkRequests.size());
-        for (final ConnectivityService.NetworkRequestInfo defaultRequest
-                : mService.mDefaultNetworkRequests) {
+        for (final NetworkRequestInfo defaultRequest : mService.mDefaultNetworkRequests) {
             final Network defaultNetwork = defaultRequest.getSatisfier() == null
                     ? null : defaultRequest.getSatisfier().network();
             // If this is the default request.
@@ -11614,6 +11797,118 @@
         // default callbacks will be unregistered in tearDown
     }
 
+    @Test
+    public void testNetworkFactoryRequestsWithMultilayerRequest()
+            throws Exception {
+        // First use OEM_PAID preference to create a multi-layer request : 1. listen for
+        // unmetered, 2. request network with cap OEM_PAID, 3, request the default network for
+        // fallback.
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+
+        final HandlerThread handlerThread = new HandlerThread("MockFactory");
+        handlerThread.start();
+        NetworkCapabilities internetFilter = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        final MockNetworkFactory internetFactory = new MockNetworkFactory(handlerThread.getLooper(),
+                mServiceContext, "internetFactory", internetFilter, mCsHandlerThread);
+        internetFactory.setScoreFilter(40);
+        internetFactory.register();
+        // Default internet request only. The unmetered request is never sent to factories (it's a
+        // LISTEN, not requestable). The 3rd (fallback) request in OEM_PAID NRI is TRACK_DEFAULT
+        // which is also not sent to factories. Finally, the OEM_PAID request doesn't match the
+        // internetFactory filter.
+        internetFactory.expectRequestAdds(1);
+        internetFactory.assertRequestCountEquals(1);
+
+        NetworkCapabilities oemPaidFilter = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_OEM_PAID)
+                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        final MockNetworkFactory oemPaidFactory = new MockNetworkFactory(handlerThread.getLooper(),
+                mServiceContext, "oemPaidFactory", oemPaidFilter, mCsHandlerThread);
+        oemPaidFactory.setScoreFilter(40);
+        oemPaidFactory.register();
+        oemPaidFactory.expectRequestAdd(); // Because nobody satisfies the request
+
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+
+        // A network connected that satisfies the default internet request. For the OEM_PAID
+        // preference, this is not as good as an OEM_PAID network, so even if the score of
+        // the network is better than the factory announced, it still should try to bring up
+        // the network.
+        expectNoRequestChanged(oemPaidFactory);
+        oemPaidFactory.assertRequestCountEquals(1);
+        // The internet factory however is outscored, and should lose its requests.
+        internetFactory.expectRequestRemove();
+        internetFactory.assertRequestCountEquals(0);
+
+        final NetworkCapabilities oemPaidNc = new NetworkCapabilities();
+        oemPaidNc.addCapability(NET_CAPABILITY_OEM_PAID);
+        oemPaidNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        final TestNetworkAgentWrapper oemPaidAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+                new LinkProperties(), oemPaidNc);
+        oemPaidAgent.connect(true);
+
+        // The oemPaidAgent has score 50/cell transport, so it beats what the oemPaidFactory can
+        // provide, therefore it loses the request.
+        oemPaidFactory.expectRequestRemove();
+        oemPaidFactory.assertRequestCountEquals(0);
+        expectNoRequestChanged(internetFactory);
+        internetFactory.assertRequestCountEquals(0);
+
+        oemPaidAgent.setScore(new NetworkScore.Builder().setLegacyInt(20).setExiting(true).build());
+        // Now the that the agent is weak, the oemPaidFactory can beat the existing network for the
+        // OEM_PAID request. The internet factory however can't beat a network that has OEM_PAID
+        // for the preference request, so it doesn't see the request.
+        oemPaidFactory.expectRequestAdd();
+        oemPaidFactory.assertRequestCountEquals(1);
+        expectNoRequestChanged(internetFactory);
+        internetFactory.assertRequestCountEquals(0);
+
+        mCellNetworkAgent.disconnect();
+        // The network satisfying the default internet request has disconnected, so the
+        // internetFactory sees the default request again. However there is a network with OEM_PAID
+        // connected, so the 2nd OEM_PAID req is already satisfied, so the oemPaidFactory doesn't
+        // care about networks that don't have OEM_PAID.
+        expectNoRequestChanged(oemPaidFactory);
+        oemPaidFactory.assertRequestCountEquals(1);
+        internetFactory.expectRequestAdd();
+        internetFactory.assertRequestCountEquals(1);
+
+        // Cell connects again, still with score 50. Back to the previous state.
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        expectNoRequestChanged(oemPaidFactory);
+        oemPaidFactory.assertRequestCountEquals(1);
+        internetFactory.expectRequestRemove();
+        internetFactory.assertRequestCountEquals(0);
+
+        // Now WiFi connects and it's unmetered, but it's weaker than cell.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).setExiting(true)
+                .build()); // Not the best Internet network, but unmetered
+        mWiFiNetworkAgent.connect(true);
+
+        // The OEM_PAID preference prefers an unmetered network to an OEM_PAID network, so
+        // the oemPaidFactory can't beat this no matter how high its score.
+        oemPaidFactory.expectRequestRemove();
+        expectNoRequestChanged(internetFactory);
+
+        mCellNetworkAgent.disconnect();
+        // Now that the best internet network (cell, with its 50 score compared to 30 for WiFi
+        // at this point), the default internet request is satisfied by a network worse than
+        // the internetFactory announced, so it gets the request. However, there is still an
+        // unmetered network, so the oemPaidNetworkFactory still can't beat this.
+        expectNoRequestChanged(oemPaidFactory);
+        internetFactory.expectRequestAdd();
+    }
+
     /**
      * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order:
      * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID
@@ -11935,11 +12230,11 @@
         testFactory.setScoreFilter(40);
 
         try {
-            // Register the factory and expect it will see default request, because all requests
-            // are sent to all factories.
+            // Register the factory. It doesn't see the default request because its filter does
+            // not include INTERNET.
             testFactory.register();
-            testFactory.expectRequestAdd();
-            testFactory.assertRequestCountEquals(1);
+            expectNoRequestChanged(testFactory);
+            testFactory.assertRequestCountEquals(0);
             // The factory won't try to start the network since the default request doesn't
             // match the filter (no INTERNET capability).
             assertFalse(testFactory.getMyStartRequested());
@@ -11952,7 +12247,7 @@
                     bestMatchingCb, mCsHandlerThread.getThreadHandler());
             bestMatchingCb.assertNoCallback();
             expectNoRequestChanged(testFactory);
-            testFactory.assertRequestCountEquals(1);
+            testFactory.assertRequestCountEquals(0);
             assertFalse(testFactory.getMyStartRequested());
 
             // Fire a normal mms request, verify the factory will only see the request.
@@ -11961,13 +12256,13 @@
                     .addCapability(NET_CAPABILITY_MMS).build();
             mCm.requestNetwork(mmsRequest, mmsNetworkCallback);
             testFactory.expectRequestAdd();
-            testFactory.assertRequestCountEquals(2);
+            testFactory.assertRequestCountEquals(1);
             assertTrue(testFactory.getMyStartRequested());
 
             // Unregister best matching callback, verify factory see no change.
             mCm.unregisterNetworkCallback(bestMatchingCb);
             expectNoRequestChanged(testFactory);
-            testFactory.assertRequestCountEquals(2);
+            testFactory.assertRequestCountEquals(1);
             assertTrue(testFactory.getMyStartRequested());
         } finally {
             testFactory.terminate();
@@ -12539,4 +12834,73 @@
             }
         }
     }
+
+    private void assertCreateNrisFromMobileDataPreferredUids(Set<Integer> uids) {
+        final Set<NetworkRequestInfo> nris =
+                mService.createNrisFromMobileDataPreferredUids(uids);
+        final NetworkRequestInfo nri = nris.iterator().next();
+        // Verify that one NRI is created with multilayer requests. Because one NRI can contain
+        // multiple uid ranges, so it only need create one NRI here.
+        assertEquals(1, nris.size());
+        assertTrue(nri.isMultilayerRequest());
+        assertEquals(nri.getUids(), uidRangesForUids(uids));
+    }
+
+    /**
+     * Test createNrisFromMobileDataPreferredUids returns correct NetworkRequestInfo.
+     */
+    @Test
+    public void testCreateNrisFromMobileDataPreferredUids() {
+        // Verify that empty uid set should not create any NRI for it.
+        final Set<NetworkRequestInfo> nrisNoUid =
+                mService.createNrisFromMobileDataPreferredUids(new ArraySet<>());
+        assertEquals(0, nrisNoUid.size());
+
+        final int uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID);
+        final int uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2);
+        final int uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID);
+        assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1));
+        assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1, uid3));
+        assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1, uid2));
+    }
+
+    private void setAndUpdateMobileDataPreferredUids(Set<Integer> uids) {
+        ConnectivitySettingsManager.setMobileDataPreferredUids(mServiceContext, uids);
+        mService.updateMobileDataPreferredUids();
+        waitForIdle();
+    }
+
+    /**
+     * Test that MOBILE_DATA_PREFERRED_UIDS changes will send correct net id and uid ranges to netd.
+     */
+    @Test
+    public void testMobileDataPreferredUidsChanged() throws Exception {
+        final InOrder inorder = inOrder(mMockNetd);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        waitForIdle();
+
+        final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+        inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
+                cellNetId, INetd.PERMISSION_NONE));
+
+        // Initial mobile data preferred uids status.
+        setAndUpdateMobileDataPreferredUids(Set.of());
+        inorder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any());
+        inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any());
+
+        final Set<Integer> uids1 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID));
+        final UidRangeParcel[] uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids1));
+        setAndUpdateMobileDataPreferredUids(uids1);
+        inorder.verify(mMockNetd, times(1)).networkAddUidRanges(cellNetId, uidRanges1);
+        inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any());
+
+        final Set<Integer> uids2 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID),
+                PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2),
+                SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID));
+        final UidRangeParcel[] uidRanges2 = toUidRangeStableParcels(uidRangesForUids(uids2));
+        setAndUpdateMobileDataPreferredUids(uids2);
+        inorder.verify(mMockNetd, times(1)).networkRemoveUidRanges(cellNetId, uidRanges1);
+        inorder.verify(mMockNetd, times(1)).networkAddUidRanges(cellNetId, uidRanges2);
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
index eb3b4df..f0d7d86 100644
--- a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
@@ -18,6 +18,7 @@
 
 import android.net.NetworkAgentConfig
 import android.net.NetworkCapabilities
+import android.net.NetworkScore.KEEP_CONNECTED_NONE
 import android.text.TextUtils
 import android.util.ArraySet
 import androidx.test.filters.SmallTest
@@ -55,16 +56,16 @@
             if (vpn) addTransportType(NetworkCapabilities.TRANSPORT_VPN)
             if (validated) addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
         }.build()
-        return mixInScore(nc, nac)
+        return mixInScore(nc, nac, false /* avoidBadWifi */)
     }
 
     @Test
     fun testGetLegacyInt() {
-        val ns = FullScore(50, 0L /* policy */)
+        val ns = FullScore(50, 0L /* policy */, KEEP_CONNECTED_NONE)
         assertEquals(10, ns.legacyInt) // -40 penalty for not being validated
         assertEquals(50, ns.legacyIntAsValidated)
 
-        val vpnNs = FullScore(101, 0L /* policy */).withPolicies(vpn = true)
+        val vpnNs = FullScore(101, 0L /* policy */, KEEP_CONNECTED_NONE).withPolicies(vpn = true)
         assertEquals(101, vpnNs.legacyInt) // VPNs are not subject to unvalidation penalty
         assertEquals(101, vpnNs.legacyIntAsValidated)
         assertEquals(101, vpnNs.withPolicies(validated = true).legacyInt)
@@ -83,7 +84,7 @@
 
     @Test
     fun testToString() {
-        val string = FullScore(10, 0L /* policy */)
+        val string = FullScore(10, 0L /* policy */, KEEP_CONNECTED_NONE)
                 .withPolicies(vpn = true, acceptUnvalidated = true).toString()
         assertTrue(string.contains("Score(10"), string)
         assertTrue(string.contains("ACCEPT_UNVALIDATED"), string)
@@ -107,7 +108,7 @@
 
     @Test
     fun testHasPolicy() {
-        val ns = FullScore(50, 0L /* policy */)
+        val ns = FullScore(50, 0L /* policy */, KEEP_CONNECTED_NONE)
         assertFalse(ns.hasPolicy(POLICY_IS_VALIDATED))
         assertFalse(ns.hasPolicy(POLICY_IS_VPN))
         assertFalse(ns.hasPolicy(POLICY_EVER_USER_SELECTED))
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt
new file mode 100644
index 0000000..409f8c3
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 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.connectivity
+
+import android.net.INetworkOfferCallback
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.net.NetworkScore.KEEP_CONNECTED_NONE
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+const val POLICY_NONE = 0L
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkOfferTest {
+    val mockCallback = mock(INetworkOfferCallback::class.java)
+
+    @Test
+    fun testOfferNeededUnneeded() {
+        val score = FullScore(50, POLICY_NONE, KEEP_CONNECTED_NONE)
+        val offer = NetworkOffer(score, NetworkCapabilities.Builder().build(), mockCallback,
+                1 /* providerId */)
+        val request1 = mock(NetworkRequest::class.java)
+        val request2 = mock(NetworkRequest::class.java)
+        offer.onNetworkNeeded(request1)
+        verify(mockCallback).onNetworkNeeded(eq(request1))
+        assertTrue(offer.neededFor(request1))
+        assertFalse(offer.neededFor(request2))
+
+        offer.onNetworkNeeded(request2)
+        verify(mockCallback).onNetworkNeeded(eq(request2))
+        assertTrue(offer.neededFor(request1))
+        assertTrue(offer.neededFor(request2))
+
+        // Note that the framework never calls onNetworkNeeded multiple times with the same
+        // request without calling onNetworkUnneeded first. It would be incorrect usage and the
+        // behavior would be undefined, so there is nothing to test.
+
+        offer.onNetworkUnneeded(request1)
+        verify(mockCallback).onNetworkUnneeded(eq(request1))
+        assertFalse(offer.neededFor(request1))
+        assertTrue(offer.neededFor(request2))
+
+        offer.onNetworkUnneeded(request2)
+        verify(mockCallback).onNetworkUnneeded(eq(request2))
+        assertFalse(offer.neededFor(request1))
+        assertFalse(offer.neededFor(request2))
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index 86c9116..1348c6a 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -43,7 +43,7 @@
         val nais = scores.map { makeNai(true, it) }
         val bestNetwork = nais[2] // The one with the top score
         val someRequest = mock(NetworkRequest::class.java)
-        assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais))
+        assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, bestNetwork))
     }
 
     @Test
@@ -52,20 +52,20 @@
                 makeNai(false, 60), makeNai(true, 23), makeNai(false, 68))
         val bestNetwork = nais[1] // Top score that's satisfying
         val someRequest = mock(NetworkRequest::class.java)
-        assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais))
+        assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, nais[1]))
     }
 
     @Test
     fun testNoMatch() {
         val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90))
         val someRequest = mock(NetworkRequest::class.java)
-        assertNull(ranker.getBestNetwork(someRequest, nais))
+        assertNull(ranker.getBestNetwork(someRequest, nais, null))
     }
 
     @Test
     fun testEmpty() {
         val someRequest = mock(NetworkRequest::class.java)
-        assertNull(ranker.getBestNetwork(someRequest, emptyList()))
+        assertNull(ranker.getBestNetwork(someRequest, emptyList(), null))
     }
 
     // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST
@@ -75,10 +75,10 @@
         val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30),
                 makeNai(true, 30), makeNai(true, 30), makeNai(true, 30))
         val someRequest = mock(NetworkRequest::class.java)
-        assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1))
+        assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1, nais1[0]))
 
         val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20),
                 makeNai(true, 50), makeNai(true, 50), makeNai(true, 40))
-        assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2))
+        assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2, nais2[1]))
     }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index c75618f..c6e7606 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -30,7 +30,7 @@
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
-import static android.net.ConnectivitySettingsManager.APPS_ALLOWED_ON_RESTRICTED_NETWORKS;
+import static android.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.os.Process.SYSTEM_UID;
 
@@ -142,7 +142,7 @@
         final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mContext));
         doReturn(UserHandle.ALL).when(asUserCtx).getUser();
         when(mContext.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
-        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
+        when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
 
         mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps));
 
@@ -341,9 +341,9 @@
     }
 
     @Test
-    public void testHasRestrictedNetworkPermissionAppAllowedOnRestrictedNetworks() {
-        mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
-                new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+    public void testHasRestrictedNetworkPermissionUidAllowedOnRestrictedNetworks() {
+        mPermissionMonitor.updateUidsAllowedOnRestrictedNetworks(
+                new ArraySet<>(new Integer[] { MOCK_UID1 }));
         assertTrue(hasRestrictedNetworkPermission(
                 PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE1, MOCK_UID1));
         assertTrue(hasRestrictedNetworkPermission(
@@ -352,11 +352,11 @@
                 PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE1, MOCK_UID1, CONNECTIVITY_INTERNAL));
 
         assertFalse(hasRestrictedNetworkPermission(
-                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1));
+                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID2));
         assertFalse(hasRestrictedNetworkPermission(
-                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1, CHANGE_NETWORK_STATE));
+                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID2, CHANGE_NETWORK_STATE));
         assertFalse(hasRestrictedNetworkPermission(
-                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1, CONNECTIVITY_INTERNAL));
+                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID2, CONNECTIVITY_INTERNAL));
 
     }
 
@@ -396,32 +396,32 @@
         assertFalse(wouldBeCarryoverPackage(PARTITION_PRODUCT, VERSION_Q, MOCK_UID1));
     }
 
-    private boolean wouldBeAppAllowedOnRestrictedNetworks(String packageName) {
-        final PackageInfo packageInfo = new PackageInfo();
-        packageInfo.packageName = packageName;
-        return mPermissionMonitor.isAppAllowedOnRestrictedNetworks(packageInfo);
+    private boolean wouldBeUidAllowedOnRestrictedNetworks(int uid) {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = uid;
+        return mPermissionMonitor.isUidAllowedOnRestrictedNetworks(applicationInfo);
     }
 
     @Test
     public void testIsAppAllowedOnRestrictedNetworks() {
-        mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(new ArraySet<>());
-        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
-        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+        mPermissionMonitor.updateUidsAllowedOnRestrictedNetworks(new ArraySet<>());
+        assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID1));
+        assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID2));
 
-        mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
-                new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
-        assertTrue(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
-        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+        mPermissionMonitor.updateUidsAllowedOnRestrictedNetworks(
+                new ArraySet<>(new Integer[] { MOCK_UID1 }));
+        assertTrue(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID1));
+        assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID2));
 
-        mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
-                new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
-        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
-        assertTrue(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+        mPermissionMonitor.updateUidsAllowedOnRestrictedNetworks(
+                new ArraySet<>(new Integer[] { MOCK_UID2 }));
+        assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID1));
+        assertTrue(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID2));
 
-        mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
-                new ArraySet<>(new String[] { "com.android.test" }));
-        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
-        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+        mPermissionMonitor.updateUidsAllowedOnRestrictedNetworks(
+                new ArraySet<>(new Integer[] { 123 }));
+        assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID1));
+        assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID2));
     }
 
     private void assertBackgroundPermission(boolean hasPermission, String name, int uid,
@@ -901,12 +901,12 @@
     }
 
     @Test
-    public void testAppsAllowedOnRestrictedNetworksChanged() throws Exception {
+    public void testUidsAllowedOnRestrictedNetworksChanged() throws Exception {
         final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
         final ArgumentCaptor<ContentObserver> captor =
                 ArgumentCaptor.forClass(ContentObserver.class);
         verify(mDeps, times(1)).registerContentObserver(any(),
-                argThat(uri -> uri.getEncodedPath().contains(APPS_ALLOWED_ON_RESTRICTED_NETWORKS)),
+                argThat(uri -> uri.getEncodedPath().contains(UIDS_ALLOWED_ON_RESTRICTED_NETWORKS)),
                 anyBoolean(), captor.capture());
         final ContentObserver contentObserver = captor.getValue();
 
@@ -924,24 +924,24 @@
         when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
         when(mPackageManager.getPackagesForUid(MOCK_UID2)).thenReturn(new String[]{MOCK_PACKAGE2});
 
-        // MOCK_PACKAGE1 is listed in setting that allow to use restricted networks, MOCK_UID1
+        // MOCK_UID1 is listed in setting that allow to use restricted networks, MOCK_UID1
         // should have SYSTEM permission.
-        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
-                new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+        when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(
+                new ArraySet<>(new Integer[] { MOCK_UID1 }));
         contentObserver.onChange(true /* selfChange */);
         mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
         mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2});
 
-        // MOCK_PACKAGE2 is listed in setting that allow to use restricted networks, MOCK_UID2
+        // MOCK_UID2 is listed in setting that allow to use restricted networks, MOCK_UID2
         // should have SYSTEM permission but MOCK_UID1 should revoke permission.
-        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
-                new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
+        when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(
+                new ArraySet<>(new Integer[] { MOCK_UID2 }));
         contentObserver.onChange(true /* selfChange */);
         mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2});
         mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
 
-        // No app lists in setting, should revoke permission from all uids.
-        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
+        // No uid lists in setting, should revoke permission from all uids.
+        when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
         contentObserver.onChange(true /* selfChange */);
         mNetdMonitor.expectNoPermission(
                 new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1, MOCK_UID2});
@@ -953,7 +953,7 @@
         final ArgumentCaptor<ContentObserver> captor =
                 ArgumentCaptor.forClass(ContentObserver.class);
         verify(mDeps, times(1)).registerContentObserver(any(),
-                argThat(uri -> uri.getEncodedPath().contains(APPS_ALLOWED_ON_RESTRICTED_NETWORKS)),
+                argThat(uri -> uri.getEncodedPath().contains(UIDS_ALLOWED_ON_RESTRICTED_NETWORKS)),
                 anyBoolean(), captor.capture());
         final ContentObserver contentObserver = captor.getValue();
 
@@ -974,22 +974,15 @@
         addPackageForUsers(new UserHandle[]{MOCK_USER1}, MOCK_PACKAGE1, MOCK_UID1);
         mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
 
-        // MOCK_PACKAGE2 is listed in setting that allow to use restricted networks, MOCK_UID1
+        // MOCK_UID1 is listed in setting that allow to use restricted networks, MOCK_UID1
         // should upgrade to SYSTEM permission.
-        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
-                new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
-        contentObserver.onChange(true /* selfChange */);
-        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
-
-        // MOCK_PACKAGE1 is listed in setting that allow to use restricted networks, MOCK_UID1
-        // should still have SYSTEM permission.
-        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
-                new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+        when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(
+                new ArraySet<>(new Integer[] { MOCK_UID1 }));
         contentObserver.onChange(true /* selfChange */);
         mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
 
         // No app lists in setting, MOCK_UID1 should downgrade to NETWORK permission.
-        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
+        when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
         contentObserver.onChange(true /* selfChange */);
         mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
 
@@ -998,4 +991,4 @@
         removePackageForUsers(new UserHandle[]{MOCK_USER1}, MOCK_PACKAGE1, MOCK_UID1);
         mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
     }
-}
\ No newline at end of file
+}