Merge changes Id5e5b911,I8301abad,I7262811a,I658e579b,I7d43ef0e am: 4fcb8ad292

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/1958935

Change-Id: I689801db7f5ea93167f62a93ee6c8f4ff89533aa
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 8da421d..5cfab5d 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -132,6 +132,7 @@
   }
 
   public final class NetworkCapabilities implements android.os.Parcelable {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public java.util.Set<java.lang.Integer> getAccessUids();
     method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
     method public boolean hasForbiddenCapability(int);
     field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
@@ -143,6 +144,7 @@
   }
 
   public static final class NetworkCapabilities.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAccessUids(@NonNull java.util.Set<java.lang.Integer>);
     method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
   }
 
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 4ae3a06..4254a66 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -264,6 +264,7 @@
         mTransportInfo = null;
         mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
         mUids = null;
+        mAccessUids.clear();
         mAdministratorUids = new int[0];
         mOwnerUid = Process.INVALID_UID;
         mSSID = null;
@@ -294,6 +295,7 @@
         }
         mSignalStrength = nc.mSignalStrength;
         mUids = (nc.mUids == null) ? null : new ArraySet<>(nc.mUids);
+        setAccessUids(nc.mAccessUids);
         setAdministratorUids(nc.getAdministratorUids());
         mOwnerUid = nc.mOwnerUid;
         mForbiddenNetworkCapabilities = nc.mForbiddenNetworkCapabilities;
@@ -1025,6 +1027,7 @@
         final int[] originalAdministratorUids = getAdministratorUids();
         final TransportInfo originalTransportInfo = getTransportInfo();
         final Set<Integer> originalSubIds = getSubscriptionIds();
+        final Set<Integer> originalAccessUids = new ArraySet<>(mAccessUids);
         clearAll();
         if (0 != (originalCapabilities & (1 << NET_CAPABILITY_NOT_RESTRICTED))) {
             // If the test network is not restricted, then it is only allowed to declare some
@@ -1044,6 +1047,7 @@
         mNetworkSpecifier = originalSpecifier;
         mSignalStrength = originalSignalStrength;
         mTransportInfo = originalTransportInfo;
+        mAccessUids.addAll(originalAccessUids);
 
         // Only retain the owner and administrator UIDs if they match the app registering the remote
         // caller that registered the network.
@@ -1809,6 +1813,86 @@
     }
 
     /**
+     * List of UIDs that can always access this network.
+     * <p>
+     * UIDs in this list have access to this network, even if the network doesn't have the
+     * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the
+     * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission.
+     * This is only useful for restricted networks. For non-restricted networks it has no effect.
+     * <p>
+     * This is disallowed in {@link NetworkRequest}, and can only be set by network agents. Network
+     * agents also have restrictions on how they can set these ; they can only back a public
+     * Android API. As such, Ethernet agents can set this when backing the per-UID access API, and
+     * Telephony can set exactly one UID which has to match the manager app for the associated
+     * subscription. Failure to comply with these rules will see this member cleared.
+     * <p>
+     * This member is never null, but can be empty.
+     * @hide
+     */
+    @NonNull
+    private final ArraySet<Integer> mAccessUids = new ArraySet<>();
+
+    /**
+     * Set the list of UIDs that can always access this network.
+     * @param uids
+     * @hide
+     */
+    public void setAccessUids(@NonNull final Set<Integer> uids) {
+        // could happen with nc.set(nc), cheaper than always making a defensive copy
+        if (uids == mAccessUids) return;
+
+        Objects.requireNonNull(uids);
+        mAccessUids.clear();
+        mAccessUids.addAll(uids);
+    }
+
+    /**
+     * The list of UIDs that can always access this network.
+     *
+     * The UIDs in this list can always access this network, even if it is restricted and
+     * the UID doesn't hold the USE_RESTRICTED_NETWORKS permission. This is defined by the
+     * network agent in charge of creating the network.
+     *
+     * The UIDs are only visible to network factories and the system server, since the system
+     * server makes sure to redact them before sending a NetworkCapabilities to a process
+     * that doesn't hold the permission.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public @NonNull Set<Integer> getAccessUids() {
+        return new ArraySet<>(mAccessUids);
+    }
+
+    /** @hide */
+    // For internal clients that know what they are doing and need to avoid the performance hit
+    // of the defensive copy.
+    public @NonNull ArraySet<Integer> getAccessUidsNoCopy() {
+        return mAccessUids;
+    }
+
+    /**
+     * Test whether this UID has special permission to access this network, as per mAccessUids.
+     * @hide
+     */
+    public boolean isAccessUid(int uid) {
+        return mAccessUids.contains(uid);
+    }
+
+    /**
+     * @return whether any UID is in the list of access UIDs
+     * @hide
+     */
+    public boolean hasAccessUids() {
+        return !mAccessUids.isEmpty();
+    }
+
+    private boolean equalsAccessUids(@NonNull NetworkCapabilities other) {
+        return mAccessUids.equals(other.mAccessUids);
+    }
+
+    /**
      * The SSID of the network, or null if not applicable or unknown.
      * <p>
      * This is filled in by wifi code.
@@ -1962,6 +2046,7 @@
                 && equalsSpecifier(that)
                 && equalsTransportInfo(that)
                 && equalsUids(that)
+                && equalsAccessUids(that)
                 && equalsSSID(that)
                 && equalsOwnerUid(that)
                 && equalsPrivateDnsBroken(that)
@@ -1986,15 +2071,16 @@
                 + mSignalStrength * 29
                 + mOwnerUid * 31
                 + Objects.hashCode(mUids) * 37
-                + Objects.hashCode(mSSID) * 41
-                + Objects.hashCode(mTransportInfo) * 43
-                + Objects.hashCode(mPrivateDnsBroken) * 47
-                + Objects.hashCode(mRequestorUid) * 53
-                + Objects.hashCode(mRequestorPackageName) * 59
-                + Arrays.hashCode(mAdministratorUids) * 61
-                + Objects.hashCode(mSubIds) * 67
-                + Objects.hashCode(mUnderlyingNetworks) * 71
-                + mEnterpriseId * 73;
+                + Objects.hashCode(mAccessUids) * 41
+                + Objects.hashCode(mSSID) * 43
+                + Objects.hashCode(mTransportInfo) * 47
+                + Objects.hashCode(mPrivateDnsBroken) * 53
+                + Objects.hashCode(mRequestorUid) * 59
+                + Objects.hashCode(mRequestorPackageName) * 61
+                + Arrays.hashCode(mAdministratorUids) * 67
+                + Objects.hashCode(mSubIds) * 71
+                + Objects.hashCode(mUnderlyingNetworks) * 73
+                + mEnterpriseId * 79;
     }
 
     @Override
@@ -2022,6 +2108,7 @@
         dest.writeParcelable((Parcelable) mTransportInfo, flags);
         dest.writeInt(mSignalStrength);
         writeParcelableArraySet(dest, mUids, flags);
+        dest.writeIntArray(CollectionUtils.toIntArray(mAccessUids));
         dest.writeString(mSSID);
         dest.writeBoolean(mPrivateDnsBroken);
         dest.writeIntArray(getAdministratorUids());
@@ -2034,7 +2121,7 @@
     }
 
     public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
-        new Creator<NetworkCapabilities>() {
+            new Creator<>() {
             @Override
             public NetworkCapabilities createFromParcel(Parcel in) {
                 NetworkCapabilities netCap = new NetworkCapabilities();
@@ -2048,6 +2135,11 @@
                 netCap.mTransportInfo = in.readParcelable(null);
                 netCap.mSignalStrength = in.readInt();
                 netCap.mUids = readParcelableArraySet(in, null /* ClassLoader, null for default */);
+                final int[] accessUids = in.createIntArray();
+                netCap.mAccessUids.ensureCapacity(accessUids.length);
+                for (int uid : accessUids) {
+                    netCap.mAccessUids.add(uid);
+                }
                 netCap.mSSID = in.readString();
                 netCap.mPrivateDnsBroken = in.readBoolean();
                 netCap.setAdministratorUids(in.createIntArray());
@@ -2124,6 +2216,11 @@
                 sb.append(" Uids: <").append(mUids).append(">");
             }
         }
+
+        if (hasAccessUids()) {
+            sb.append(" AccessUids: <").append(mAccessUids).append(">");
+        }
+
         if (mOwnerUid != Process.INVALID_UID) {
             sb.append(" OwnerUid: ").append(mOwnerUid);
         }
@@ -2300,7 +2397,7 @@
 
     private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
         if (!isValidCapability(capability)) {
-            throw new IllegalArgumentException("NetworkCapability " + capability + "out of range");
+            throw new IllegalArgumentException("NetworkCapability " + capability + " out of range");
         }
     }
 
@@ -2909,6 +3006,44 @@
         }
 
         /**
+         * Set a list of UIDs that can always access this network
+         * <p>
+         * Provide a list of UIDs that can access this network even if the network doesn't have the
+         * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the
+         * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission.
+         * <p>
+         * This is disallowed in {@link NetworkRequest}, and can only be set by
+         * {@link NetworkAgent}s, who hold the
+         * {@link android.Manifest.permission.NETWORK_FACTORY} permission.
+         * Network agents also have restrictions on how they can set these ; they can only back
+         * a public Android API. As such, Ethernet agents can set this when backing the per-UID
+         * access API, and Telephony can set exactly one UID which has to match the manager app for
+         * the associated subscription. Failure to comply with these rules will see this member
+         * cleared.
+         * <p>
+         * These UIDs are only visible to network factories and the system server, since the system
+         * server makes sure to redact them before sending a {@link NetworkCapabilities} instance
+         * to a process that doesn't hold the {@link android.Manifest.permission.NETWORK_FACTORY}
+         * permission.
+         * <p>
+         * This list cannot be null, but it can be empty to mean that no UID without the
+         * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission
+         * gets to access this network.
+         *
+         * @param uids the list of UIDs that can always access this network
+         * @return this builder
+         * @hide
+         */
+        @NonNull
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+        public Builder setAccessUids(@NonNull Set<Integer> uids) {
+            Objects.requireNonNull(uids);
+            mCaps.setAccessUids(uids);
+            return this;
+        }
+
+        /**
          * Set the underlying networks of this network.
          *
          * @param networks The underlying networks of this network.
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 8137551..f587cf0 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -439,8 +439,6 @@
      * Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
      * The default request uses PREFERENCE_ORDER_DEFAULT.
      */
-    // Bound for the lowest valid preference order.
-    static final int PREFERENCE_ORDER_LOWEST = 999;
     // Used when sending to netd to code for "no order".
     static final int PREFERENCE_ORDER_NONE = 0;
     // Order for requests that don't code for a per-app preference. As it is
@@ -448,11 +446,6 @@
     // PREFERENCE_ORDER_NONE when sending to netd.
     @VisibleForTesting
     static final int PREFERENCE_ORDER_INVALID = Integer.MAX_VALUE;
-    // Order for the default internet request. Since this must always have the
-    // lowest priority, its value is larger than the largest acceptable value. As
-    // it is out of the valid range, the corresponding order should be
-    // PREFERENCE_ORDER_NONE when sending to netd.
-    static final int PREFERENCE_ORDER_DEFAULT = 1000;
     // As a security feature, VPNs have the top priority.
     static final int PREFERENCE_ORDER_VPN = 0; // Netd supports only 0 for VPN.
     // Order of per-app OEM preference. See {@link #setOemNetworkPreference}.
@@ -467,6 +460,13 @@
     // See {@link ConnectivitySettingsManager#setMobileDataPreferredUids}
     @VisibleForTesting
     static final int PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED = 30;
+    // Preference order that signifies the network shouldn't be set as a default network for
+    // the UIDs, only give them access to it. TODO : replace this with a boolean
+    // in NativeUidRangeConfig
+    @VisibleForTesting
+    static final int PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT = 999;
+    // Bound for the lowest valid preference order.
+    static final int PREFERENCE_ORDER_LOWEST = 999;
 
     /**
      * used internally to clear a wakelock when transitioning
@@ -2099,6 +2099,7 @@
         newNc.setAdministratorUids(new int[0]);
         if (!checkAnyPermissionOf(
                 callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) {
+            newNc.setAccessUids(new ArraySet<>());
             newNc.setSubscriptionIds(Collections.emptySet());
         }
 
@@ -3348,18 +3349,8 @@
 
             switch (msg.what) {
                 case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
-                    NetworkCapabilities networkCapabilities = (NetworkCapabilities) arg.second;
-                    if (networkCapabilities.hasConnectivityManagedCapability()) {
-                        Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
-                    }
-                    if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
-                        // Make sure the original object is not mutated. NetworkAgent normally
-                        // makes a copy of the capabilities when sending the message through
-                        // the Messenger, but if this ever changes, not making a defensive copy
-                        // here will give attack vectors to clients using this code path.
-                        networkCapabilities = new NetworkCapabilities(networkCapabilities);
-                        networkCapabilities.restrictCapabilitiesForTestNetwork(nai.creatorUid);
-                    }
+                    final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
+                            (NetworkCapabilities) arg.second);
                     processCapabilitiesFromAgent(nai, networkCapabilities);
                     updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
                     break;
@@ -6220,6 +6211,9 @@
         if (nc.isPrivateDnsBroken()) {
             throw new IllegalArgumentException("Can't request broken private DNS");
         }
+        if (nc.hasAccessUids()) {
+            throw new IllegalArgumentException("Can't request access UIDs");
+        }
     }
 
     // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@@ -6454,9 +6448,10 @@
         ensureRequestableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
                 Binder.getCallingPid(), callingUid, callingPackageName);
-        ensureValidNetworkSpecifier(networkCapabilities);
         restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
                 callingUid, callingPackageName);
+        ensureValid(networkCapabilities);
+
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
         NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation,
@@ -6976,28 +6971,18 @@
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
             NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, int providerId,
             int uid) {
-        if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
-            // Strictly, sanitizing here is unnecessary as the capabilities will be sanitized in
-            // the call to mixInCapabilities below anyway, but sanitizing here means the NAI never
-            // sees capabilities that may be malicious, which might prevent mistakes in the future.
-            networkCapabilities = new NetworkCapabilities(networkCapabilities);
-            networkCapabilities.restrictCapabilitiesForTestNetwork(uid);
-        }
 
-        LinkProperties lp = new LinkProperties(linkProperties);
-
-        final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
+        // At this point the capabilities/properties are untrusted and unverified, e.g. checks that
+        // the capabilities' access UID comply with security limitations. They will be sanitized
+        // as the NAI registration finishes, in handleRegisterNetworkAgent(). This is
+        // because some of the checks must happen on the handler thread.
         final NetworkAgentInfo nai = new NetworkAgentInfo(na,
-                new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
+                new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo),
+                linkProperties, networkCapabilities,
                 currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
                 this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs,
                 mQosCallbackTracker, mDeps);
 
-        // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
-        processCapabilitiesFromAgent(nai, nc);
-        nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
-        processLinkPropertiesFromAgent(nai, nai.linkProperties);
-
         final String extraInfo = networkInfo.getExtraInfo();
         final String name = TextUtils.isEmpty(extraInfo)
                 ? nai.networkCapabilities.getSsid() : extraInfo;
@@ -7012,8 +6997,20 @@
     }
 
     private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
+        if (VDBG) log("Network Monitor created for " +  nai);
+        // nai.nc and nai.lp are the same object that was passed by the network agent if the agent
+        // lives in the same process as this code (e.g. wifi), so make sure this code doesn't
+        // mutate their object
+        final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
+        final LinkProperties lp = new LinkProperties(nai.linkProperties);
+        // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
+        processCapabilitiesFromAgent(nai, nc);
+        nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
+        processLinkPropertiesFromAgent(nai, lp);
+        nai.linkProperties = lp;
+
         nai.onNetworkMonitorCreated(networkMonitor);
-        if (VDBG) log("Got NetworkAgent Messenger");
+
         mNetworkAgentInfos.add(nai);
         synchronized (mNetworkForNetId) {
             mNetworkForNetId.put(nai.network.getNetId(), nai);
@@ -7024,6 +7021,7 @@
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
         }
+
         nai.notifyRegistered();
         NetworkInfo networkInfo = nai.networkInfo;
         updateNetworkInfo(nai, networkInfo);
@@ -7471,9 +7469,11 @@
      * Stores into |nai| any data coming from the agent that might also be written to the network's
      * NetworkCapabilities by ConnectivityService itself. This ensures that the data provided by the
      * agent is not lost when updateCapabilities is called.
-     * This method should never alter the agent's NetworkCapabilities, only store data in |nai|.
      */
     private void processCapabilitiesFromAgent(NetworkAgentInfo nai, NetworkCapabilities nc) {
+        if (nc.hasConnectivityManagedCapability()) {
+            Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
+        }
         // Note: resetting the owner UID before storing the agent capabilities in NAI means that if
         // the agent attempts to change the owner UID, then nai.declaredCapabilities will not
         // actually be the same as the capabilities sent by the agent. Still, it is safer to reset
@@ -7484,6 +7484,7 @@
             nc.setOwnerUid(nai.networkCapabilities.getOwnerUid());
         }
         nai.declaredCapabilities = new NetworkCapabilities(nc);
+        NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid);
     }
 
     /** Modifies |newNc| based on the capabilities of |underlyingNetworks| and |agentCaps|. */
@@ -7748,6 +7749,17 @@
         return stableRanges;
     }
 
+    private static UidRangeParcel[] intsToUidRangeStableParcels(
+            final @NonNull ArraySet<Integer> uids) {
+        final UidRangeParcel[] stableRanges = new UidRangeParcel[uids.size()];
+        int index = 0;
+        for (int uid : uids) {
+            stableRanges[index] = new UidRangeParcel(uid, uid);
+            index++;
+        }
+        return stableRanges;
+    }
+
     private static UidRangeParcel[] toUidRangeStableParcels(UidRange[] ranges) {
         final UidRangeParcel[] stableRanges = new UidRangeParcel[ranges.length];
         for (int i = 0; i < ranges.length; i++) {
@@ -7818,8 +7830,14 @@
         }
     }
 
-    private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
-            NetworkCapabilities newNc) {
+    private void updateUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
+            @Nullable NetworkCapabilities newNc) {
+        updateVpnUids(nai, prevNc, newNc);
+        updateAccessUids(nai, prevNc, newNc);
+    }
+
+    private void updateVpnUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
+            @Nullable NetworkCapabilities newNc) {
         Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUidRanges();
         Set<UidRange> newRanges = null == newNc ? null : newNc.getUidRanges();
         if (null == prevRanges) prevRanges = new ArraySet<>();
@@ -7878,6 +7896,46 @@
         }
     }
 
+    private void updateAccessUids(@NonNull NetworkAgentInfo nai,
+            @Nullable NetworkCapabilities prevNc, @Nullable NetworkCapabilities newNc) {
+        // In almost all cases both NC code for empty access UIDs. return as fast as possible.
+        final boolean prevEmpty = null == prevNc || prevNc.getAccessUidsNoCopy().isEmpty();
+        final boolean newEmpty = null == newNc || newNc.getAccessUidsNoCopy().isEmpty();
+        if (prevEmpty && newEmpty) return;
+
+        final ArraySet<Integer> prevUids =
+                null == prevNc ? new ArraySet<>() : prevNc.getAccessUidsNoCopy();
+        final ArraySet<Integer> newUids =
+                null == newNc ? new ArraySet<>() : newNc.getAccessUidsNoCopy();
+
+        if (prevUids.equals(newUids)) return;
+
+        // This implementation is very simple and vastly faster for sets of Integers than
+        // CompareOrUpdateResult, which is tuned for sets that need to be compared based on
+        // a key computed from the value and has storage for that.
+        final ArraySet<Integer> toRemove = new ArraySet<>(prevUids);
+        final ArraySet<Integer> toAdd = new ArraySet<>(newUids);
+        toRemove.removeAll(newUids);
+        toAdd.removeAll(prevUids);
+
+        try {
+            if (!toAdd.isEmpty()) {
+                mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
+                        nai.network.netId,
+                        intsToUidRangeStableParcels(toAdd),
+                        PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+            }
+            if (!toRemove.isEmpty()) {
+                mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+                        nai.network.netId,
+                        intsToUidRangeStableParcels(toRemove),
+                        PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+            }
+        } catch (RemoteException e) {
+            // Netd died. This usually causes a runtime restart anyway.
+        }
+    }
+
     public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
         ensureRunningOnConnectivityServiceThread();
 
@@ -9844,7 +9902,7 @@
                 android.Manifest.permission.NETWORK_STACK);
         final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
         if (!nc.hasTransport(TRANSPORT_TEST)) {
-            throw new SecurityException("Data Stall simluation is only possible for test networks");
+            throw new SecurityException("Data Stall simulation is only possible for test networks");
         }
 
         final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 1a7248a..b9e2a5f 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,7 +17,9 @@
 package com.android.server.connectivity;
 
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.transportNamesOf;
 
 import android.annotation.NonNull;
@@ -52,6 +54,7 @@
 import android.os.SystemClock;
 import android.telephony.data.EpsBearerQosSessionAttributes;
 import android.telephony.data.NrQosSessionAttributes;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -1188,6 +1191,38 @@
         return mConnectivityReport;
     }
 
+    /**
+     * Make sure the NC from network agents don't contain stuff they shouldn't.
+     *
+     * @param nc the capabilities to sanitize
+     * @param creatorUid the UID of the process creating this network agent
+     */
+    public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
+            final int creatorUid) {
+        if (nc.hasTransport(TRANSPORT_TEST)) {
+            nc.restrictCapabilitiesForTestNetwork(creatorUid);
+        }
+        if (!areAccessUidsAcceptableFromNetworkAgent(nc)) {
+            nc.setAccessUids(new ArraySet<>());
+        }
+    }
+
+    private static boolean areAccessUidsAcceptableFromNetworkAgent(
+            @NonNull final NetworkCapabilities nc) {
+        // NCs without access UIDs are fine.
+        if (!nc.hasAccessUids()) return true;
+
+        // On a non-restricted network, access UIDs make no sense
+        if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false;
+
+        // If this network has TRANSPORT_TEST, then the caller can do whatever they want to
+        // access UIDs
+        if (nc.hasTransport(TRANSPORT_TEST)) return true;
+
+        // TODO : accept more supported cases
+        return false;
+    }
+
     // TODO: Print shorter members first and only print the boolean variable which value is true
     // to improve readability.
     public String toString() {
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index bea00a9..742044b 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -308,6 +308,48 @@
         }
     }
 
+    @Test @IgnoreUpTo(SC_V2)
+    public void testSetAccessUids() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        assertThrows(NullPointerException.class, () -> nc.setAccessUids(null));
+        assertFalse(nc.hasAccessUids());
+        assertFalse(nc.isAccessUid(0));
+        assertFalse(nc.isAccessUid(1000));
+        assertEquals(0, nc.getAccessUids().size());
+        nc.setAccessUids(new ArraySet<>());
+        assertFalse(nc.hasAccessUids());
+        assertFalse(nc.isAccessUid(0));
+        assertFalse(nc.isAccessUid(1000));
+        assertEquals(0, nc.getAccessUids().size());
+
+        final ArraySet<Integer> uids = new ArraySet<>();
+        uids.add(200);
+        uids.add(250);
+        uids.add(-1);
+        uids.add(Integer.MAX_VALUE);
+        nc.setAccessUids(uids);
+        assertNotEquals(nc, new NetworkCapabilities());
+        assertTrue(nc.hasAccessUids());
+
+        final List<Integer> includedList = List.of(-2, 0, 199, 700, 901, 1000, Integer.MIN_VALUE);
+        final List<Integer> excludedList = List.of(-1, 200, 250, Integer.MAX_VALUE);
+        for (final int uid : includedList) {
+            assertFalse(nc.isAccessUid(uid));
+        }
+        for (final int uid : excludedList) {
+            assertTrue(nc.isAccessUid(uid));
+        }
+
+        final Set<Integer> outUids = nc.getAccessUids();
+        assertEquals(4, outUids.size());
+        for (final int uid : includedList) {
+            assertFalse(outUids.contains(uid));
+        }
+        for (final int uid : excludedList) {
+            assertTrue(outUids.contains(uid));
+        }
+    }
+
     @Test
     public void testParcelNetworkCapabilities() {
         final Set<Range<Integer>> uids = new ArraySet<>();
@@ -318,6 +360,10 @@
             .addCapability(NET_CAPABILITY_EIMS)
             .addCapability(NET_CAPABILITY_NOT_METERED);
         if (isAtLeastS()) {
+            final ArraySet<Integer> accessUids = new ArraySet<>();
+            accessUids.add(4);
+            accessUids.add(9);
+            netCap.setAccessUids(accessUids);
             netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2));
             netCap.setUids(uids);
         }
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index ef5dc77..344482b 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -23,7 +23,6 @@
 import android.net.INetworkAgentRegistry
 import android.net.InetAddresses
 import android.net.IpPrefix
-import android.net.KeepalivePacketData
 import android.net.LinkAddress
 import android.net.LinkProperties
 import android.net.NattKeepalivePacketData
@@ -42,6 +41,7 @@
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
 import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_TEST
 import android.net.NetworkCapabilities.TRANSPORT_VPN
 import android.net.NetworkInfo
@@ -53,7 +53,6 @@
 import android.net.QosCallback
 import android.net.QosCallbackException
 import android.net.QosCallback.QosCallbackRegistrationException
-import android.net.QosFilter
 import android.net.QosSession
 import android.net.QosSessionAttributes
 import android.net.QosSocketInfo
@@ -67,7 +66,6 @@
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
-import android.os.Looper
 import android.os.Message
 import android.os.SystemClock
 import android.telephony.TelephonyManager
@@ -82,6 +80,8 @@
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.RecorderCallback.CallbackEntry.Losing
 import com.android.testutils.RecorderCallback.CallbackEntry.Lost
 import com.android.testutils.TestableNetworkAgent
@@ -100,7 +100,6 @@
 import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
 import com.android.testutils.TestableNetworkCallback
 import org.junit.After
-import org.junit.Assert.assertArrayEquals
 import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.Test
@@ -462,6 +461,36 @@
         }
     }
 
+    private fun ncWithAccessUids(vararg uids: Int) = NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .setAccessUids(uids.toSet()).build()
+
+    @Test
+    fun testRejectedUpdates() {
+        val callback = TestableNetworkCallback()
+        // will be cleaned up in tearDown
+        registerNetworkCallback(makeTestNetworkRequest(), callback)
+        val agent = createNetworkAgent(initialNc = ncWithAccessUids(200))
+        agent.register()
+        agent.markConnected()
+
+        // Make sure the UIDs have been ignored.
+        callback.expectCallback<Available>(agent.network!!)
+        callback.expectCapabilitiesThat(agent.network!!) {
+            it.accessUids.isEmpty() && !it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        callback.expectCallback<LinkPropertiesChanged>(agent.network!!)
+        callback.expectCallback<BlockedStatus>(agent.network!!)
+        callback.expectCapabilitiesThat(agent.network!!) {
+            it.accessUids.isEmpty() && it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+
+        // Make sure that the UIDs are also ignored upon update
+        agent.sendNetworkCapabilities(ncWithAccessUids(200, 300))
+        callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+    }
+
     @Test
     fun testSendScore() {
         // This test will create two networks and check that the one with the stronger
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index 4dc86ff..365c0cf 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -109,6 +110,9 @@
             case TRANSPORT_WIFI_AWARE:
                 mScore = new NetworkScore.Builder().setLegacyInt(20).build();
                 break;
+            case TRANSPORT_TEST:
+                mScore = new NetworkScore.Builder().build();
+                break;
             case TRANSPORT_VPN:
                 mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
                 // VPNs deduce the SUSPENDED capability from their underlying networks and there
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 8f67e48..2985c41 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.GET_INTENT_SENDER_INTENT;
 import static android.Manifest.permission.LOCAL_MAC_ADDRESS;
+import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_FACTORY;
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
@@ -108,6 +109,7 @@
 import static android.net.NetworkCapabilities.REDACT_NONE;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -1490,6 +1492,10 @@
                 r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new);
     }
 
+    private UidRangeParcel[] intToUidRangeStableParcels(final @NonNull Set<Integer> ranges) {
+        return ranges.stream().map(r -> new UidRangeParcel(r, r)).toArray(UidRangeParcel[]::new);
+    }
+
     private VpnManagerService makeVpnManagerService() {
         final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() {
             public int getCallingUid() {
@@ -3813,14 +3819,14 @@
     public void testNoMutableNetworkRequests() throws Exception {
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
                 mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
-        NetworkRequest request1 = new NetworkRequest.Builder()
+        final NetworkRequest request1 = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_VALIDATED)
                 .build();
-        NetworkRequest request2 = new NetworkRequest.Builder()
+        final NetworkRequest request2 = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL)
                 .build();
 
-        Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
         assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback()));
         assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent));
         assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback()));
@@ -3828,6 +3834,36 @@
     }
 
     @Test
+    public void testNoAccessUidsInNetworkRequests() throws Exception {
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
+        final NetworkRequest r = new NetworkRequest.Builder().build();
+        final ArraySet<Integer> accessUids = new ArraySet<>();
+        accessUids.add(6);
+        accessUids.add(9);
+        r.networkCapabilities.setAccessUids(accessUids);
+
+        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        final NetworkCallback cb = new NetworkCallback();
+
+        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+        assertThrows(expected, () -> mCm.requestNetwork(r, cb));
+        assertThrows(expected, () -> mCm.requestNetwork(r, pendingIntent));
+        assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb));
+        assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb, handler));
+        assertThrows(expected, () -> mCm.registerNetworkCallback(r, pendingIntent));
+        assertThrows(expected, () -> mCm.registerBestMatchingNetworkCallback(r, cb, handler));
+
+        // Make sure that resetting the access UIDs to the empty set will allow calling
+        // requestNetwork and registerNetworkCallback.
+        r.networkCapabilities.setAccessUids(Collections.emptySet());
+        mCm.requestNetwork(r, cb);
+        mCm.unregisterNetworkCallback(cb);
+        mCm.registerNetworkCallback(r, cb);
+        mCm.unregisterNetworkCallback(cb);
+    }
+
+    @Test
     public void testMMSonWiFi() throws Exception {
         // Test bringing up cellular without MMS NetworkRequest gets reaped
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -14571,6 +14607,75 @@
                 () -> mCm.registerNetworkCallback(getRequestWithSubIds(), new NetworkCallback()));
     }
 
+    @Test
+    public void testAccessUids() throws Exception {
+        final int preferenceOrder =
+                ConnectivityService.PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT;
+        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+        mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addTransportType(TRANSPORT_TEST)
+                        .build(),
+                cb);
+
+        final ArraySet<Integer> uids = new ArraySet<>();
+        uids.add(200);
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .setAccessUids(uids)
+                .build();
+        final TestNetworkAgentWrapper agent = new TestNetworkAgentWrapper(TRANSPORT_TEST,
+                new LinkProperties(), nc);
+        agent.connect(true);
+        cb.expectAvailableThenValidatedCallbacks(agent);
+
+        final InOrder inOrder = inOrder(mMockNetd);
+        final NativeUidRangeConfig uids200Parcel = new NativeUidRangeConfig(
+                agent.getNetwork().getNetId(),
+                intToUidRangeStableParcels(uids),
+                preferenceOrder);
+        inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
+
+        uids.add(300);
+        uids.add(400);
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+
+        uids.remove(200);
+        final NativeUidRangeConfig uids300400Parcel = new NativeUidRangeConfig(
+                agent.getNetwork().getNetId(),
+                intToUidRangeStableParcels(uids),
+                preferenceOrder);
+        inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
+
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+        inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
+
+        uids.clear();
+        uids.add(600);
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+        final NativeUidRangeConfig uids600Parcel = new NativeUidRangeConfig(
+                agent.getNetwork().getNetId(),
+                intToUidRangeStableParcels(uids),
+                preferenceOrder);
+        inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
+        inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
+
+        uids.clear();
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().isEmpty());
+        inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
+    }
+
     /**
      * Validate request counts are counted accurately on setProfileNetworkPreference on set/replace.
      */