Merge "Revert "Add min_sdk_version to connectivity targets""
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/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/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/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 1a6b37b..a64a9e6 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
@@ -3364,11 +3365,11 @@
     @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"));
@@ -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..a6de173 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,25 @@
  * 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, int providerId);
 
     /**
-     * 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/NetworkProvider.java b/framework/src/android/net/NetworkProvider.java
index 8f93047..d859022 100644
--- a/framework/src/android/net/NetworkProvider.java
+++ b/framework/src/android/net/NetworkProvider.java
@@ -170,10 +170,11 @@
     /** @hide */
     // TODO : make @SystemApi when the impl is complete
     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, int providerId);
+        /** 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 +188,14 @@
         }
 
         @Override
-        public void onOfferNeeded(final @NonNull NetworkRequest request,
+        public void onNetworkNeeded(final @NonNull NetworkRequest request,
                 final int providerId) {
-            mExecutor.execute(() -> callback.onOfferNeeded(request, providerId));
+            mExecutor.execute(() -> callback.onNetworkNeeded(request, providerId));
         }
 
         @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,41 +214,41 @@
     }
 
     /**
-     * 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).
      *
@@ -272,6 +273,9 @@
     public void offerNetwork(@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 +289,8 @@
                 mProxies.add(proxy);
             }
         }
-        mContext.getSystemService(ConnectivityManager.class).offerNetwork(this, score, caps, proxy);
+        mContext.getSystemService(ConnectivityManager.class)
+                .offerNetwork(providerId, score, caps, proxy);
     }
 
     /**
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index adfcace..3771bbc 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -617,6 +617,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.
      */
@@ -1460,6 +1465,11 @@
         mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
     }
 
+    @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);
@@ -1504,6 +1514,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(
@@ -1519,6 +1531,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() {
@@ -2717,6 +2734,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();
+        }
     }
 
     /**
@@ -4801,6 +4825,9 @@
                 case EVENT_REPORT_NETWORK_ACTIVITY:
                     mNetworkActivityTracker.handleReportNetworkActivity();
                     break;
+                case EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED:
+                    handleMobileDataPreferredUidsChanged();
+                    break;
             }
         }
     }
@@ -5605,7 +5632,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();
@@ -6233,11 +6260,11 @@
     }
 
     @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) {
         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));
     }
 
@@ -6255,7 +6282,7 @@
         // 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);
             }
@@ -6319,6 +6346,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
@@ -6640,6 +6672,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
@@ -6647,7 +6686,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.
@@ -6662,7 +6701,7 @@
         }
         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;
@@ -6674,7 +6713,7 @@
     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(
@@ -9679,7 +9718,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;
@@ -9738,7 +9778,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");
@@ -9767,6 +9808,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);
@@ -9796,7 +9887,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,
@@ -9834,7 +9926,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/connectivity/NetworkOffer.java b/service/src/com/android/server/connectivity/NetworkOffer.java
index fa2d465..548db6b 100644
--- a/service/src/com/android/server/connectivity/NetworkOffer.java
+++ b/service/src/com/android/server/connectivity/NetworkOffer.java
@@ -21,7 +21,6 @@
 import android.net.INetworkOfferCallback;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.os.Messenger;
 
 import java.util.Objects;
 
@@ -44,7 +43,7 @@
     @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;
 
     private static NetworkCapabilities emptyCaps() {
         final NetworkCapabilities nc = new NetworkCapabilities();
@@ -56,11 +55,11 @@
     public NetworkOffer(@NonNull final FullScore score,
             @Nullable final NetworkCapabilities caps,
             @NonNull final INetworkOfferCallback callback,
-            @NonNull final Messenger provider) {
+            @NonNull final int providerId) {
         this.score = Objects.requireNonNull(score);
         this.caps = null != caps ? caps : emptyCaps();
         this.callback = Objects.requireNonNull(callback);
-        this.provider = Objects.requireNonNull(provider);
+        this.providerId = providerId;
     }
 
     /**
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/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/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 04e63fc..0d3cd61 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -299,7 +299,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.ConnectivityService.NetworkRequestInfo;
 import com.android.server.connectivity.ConnectivityConstants;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
@@ -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";
@@ -1170,6 +1173,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 +1543,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, "",
@@ -10050,7 +10059,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++) {
@@ -10333,8 +10342,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();
     }
 
@@ -10373,7 +10381,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));
@@ -10402,7 +10410,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));
@@ -10428,7 +10436,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));
@@ -10451,7 +10459,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));
@@ -10484,7 +10492,7 @@
                 .build();
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+        final ArraySet<NetworkRequestInfo> nris =
                 mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
 
         assertNotNull(nris);
@@ -10509,7 +10517,7 @@
                 .build();
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final List<ConnectivityService.NetworkRequestInfo> nris =
+        final List<NetworkRequestInfo> nris =
                 new ArrayList<>(
                         mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
                                 pref));
@@ -10541,7 +10549,7 @@
                 .build();
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final List<ConnectivityService.NetworkRequestInfo> nris =
+        final List<NetworkRequestInfo> nris =
                 new ArrayList<>(
                         mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
                                 pref));
@@ -10583,7 +10591,7 @@
                 .build();
 
         // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
-        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+        final ArraySet<NetworkRequestInfo> nris =
                 mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
 
         assertEquals(expectedNumOfNris, nris.size());
@@ -10676,8 +10684,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.
@@ -12506,4 +12513,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/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
+}