Support bringing up restricted wifi

This CL allows the Carrier Config app to request restricted WiFi nework
by setting the correspoding subscription ID in the network request.

Bug: 315835605
Test: atest android.net.cts.NetworkRequestTest
      atest ConnectivityCoverageTests:android.net.connectivity.com.android.server.connectivity.CarrierPrivilegeAuthenticatorTest
      atest ConnectivityCoverageTests:android.net.connectivity.com.android.server.ConnectivityServiceTest

Change-Id: I237e692d092ff5f969ce4b7962f6d58099a6f3a9
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 52f890d..0974e4b 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -113,6 +113,8 @@
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
 import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf;
 import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
+import static com.android.server.connectivity.CarrierPrivilegeAuthenticator.CarrierPrivilegesLostListener;
+import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
 
 import android.Manifest;
 import android.annotation.CheckResult;
@@ -467,6 +469,8 @@
 
     private volatile boolean mLockdownEnabled;
 
+    private final boolean mRequestRestrictedWifiEnabled;
+
     /**
      * Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in
      * internal handler thread, they don't need a lock.
@@ -832,6 +836,11 @@
     private static final int EVENT_UID_FROZEN_STATE_CHANGED = 61;
 
     /**
+     * Event to inform the ConnectivityService handler when a uid has lost carrier privileges.
+     */
+    private static final int EVENT_UID_CARRIER_PRIVILEGES_LOST = 62;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -1270,6 +1279,18 @@
     }
     private final LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(this);
 
+    private final CarrierPrivilegesLostListenerImpl mCarrierPrivilegesLostListenerImpl =
+            new CarrierPrivilegesLostListenerImpl();
+
+    private class CarrierPrivilegesLostListenerImpl implements CarrierPrivilegesLostListener {
+        @Override
+        public void onCarrierPrivilegesLost(int uid) {
+            if (mRequestRestrictedWifiEnabled) {
+                mHandler.sendMessage(mHandler.obtainMessage(
+                        EVENT_UID_CARRIER_PRIVILEGES_LOST, uid, 0 /* arg2 */));
+            }
+        }
+    }
     final LocalPriorityDump mPriorityDumper = new LocalPriorityDump();
     /**
      * Helper class which parses out priority arguments and dumps sections according to their
@@ -1328,6 +1349,11 @@
         }
     }
 
+    @VisibleForTesting
+    CarrierPrivilegesLostListener getCarrierPrivilegesLostListener() {
+        return mCarrierPrivilegesLostListenerImpl;
+    }
+
     /**
      * Dependencies of ConnectivityService, for injection in tests.
      */
@@ -1488,9 +1514,13 @@
          */
         @Nullable
         public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
-                @NonNull final Context context, @NonNull final TelephonyManager tm) {
+                @NonNull final Context context,
+                @NonNull final TelephonyManager tm,
+                boolean requestRestrictedWifiEnabled,
+                @NonNull CarrierPrivilegesLostListener listener) {
             if (isAtLeastT()) {
-                return new CarrierPrivilegeAuthenticator(context, tm);
+                return new CarrierPrivilegeAuthenticator(
+                        context, tm, requestRestrictedWifiEnabled, listener);
             } else {
                 return null;
             }
@@ -1759,8 +1789,11 @@
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
-        mCarrierPrivilegeAuthenticator =
-                mDeps.makeCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
+        mRequestRestrictedWifiEnabled = mDeps.isAtLeastU()
+                && mDeps.isFeatureEnabled(context, REQUEST_RESTRICTED_WIFI);
+        mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
+                mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
+                mCarrierPrivilegesLostListenerImpl);
 
         // To ensure uid state is synchronized with Network Policy, register for
         // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -6410,6 +6443,9 @@
                     UidFrozenStateChangedArgs args = (UidFrozenStateChangedArgs) msg.obj;
                     handleFrozenUids(args.mUids, args.mFrozenStates);
                     break;
+                case EVENT_UID_CARRIER_PRIVILEGES_LOST:
+                    handleUidCarrierPrivilegesLost(msg.arg1);
+                    break;
             }
         }
     }
@@ -7490,9 +7526,14 @@
         }
         mAppOpsManager.checkPackage(callerUid, callerPackageName);
 
-        if (!nc.getSubscriptionIds().isEmpty()) {
-            enforceNetworkFactoryPermission();
+        if (nc.getSubscriptionIds().isEmpty()) {
+            return;
         }
+        if (mRequestRestrictedWifiEnabled
+                && canRequestRestrictedNetworkDueToCarrierPrivileges(nc, callerUid)) {
+            return;
+        }
+        enforceNetworkFactoryPermission();
     }
 
     private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) {
@@ -7772,6 +7813,22 @@
         applicationNetworkCapabilities.enforceSelfCertifiedNetworkCapabilitiesDeclared(
                 networkCapabilities);
     }
+
+    private boolean canRequestRestrictedNetworkDueToCarrierPrivileges(
+            NetworkCapabilities networkCapabilities, int callingUid) {
+        if (mRequestRestrictedWifiEnabled) {
+            // For U+ devices, callers with carrier privilege could request restricted networks
+            // with CBS capabilities, or any restricted WiFi networks.
+            return ((networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
+                || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities));
+        } else {
+            // For T+ devices, callers with carrier privilege could request with CBS
+            // capabilities.
+            return (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
+                && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities));
+        }
+    }
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
             String callingPackageName, String callingAttributionTag, final int callingUid) {
         if (shouldCheckCapabilitiesDeclaration(networkCapabilities, callingUid,
@@ -7779,13 +7836,11 @@
             enforceRequestCapabilitiesDeclaration(callingPackageName, networkCapabilities,
                     callingUid);
         }
-        if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
-            // For T+ devices, callers with carrier privilege could request with CBS capabilities.
-            if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
-                    && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities)) {
-                return;
+        if (!networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
+            if (!canRequestRestrictedNetworkDueToCarrierPrivileges(
+                    networkCapabilities, callingUid)) {
+                enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
             }
-            enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
         } else {
             enforceChangePermission(callingPackageName, callingAttributionTag);
         }
@@ -9052,6 +9107,38 @@
         }
     }
 
+    private void handleUidCarrierPrivilegesLost(int uid) {
+        ensureRunningOnConnectivityServiceThread();
+        // A NetworkRequest needs to be revoked when all the conditions are met
+        //   1. It requests restricted network
+        //   2. The requestor uid matches the uid with the callback
+        //   3. The app doesn't have Carrier Privileges
+        //   4. The app doesn't have permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
+        for (final NetworkRequest nr : mNetworkRequests.keySet()) {
+            if ((nr.isRequest() || nr.isListen())
+                    && !nr.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                    && nr.getRequestorUid() == uid
+                    && !hasConnectivityRestrictedNetworksPermission(uid, true)) {
+                declareNetworkRequestUnfulfillable(nr);
+            }
+        }
+
+        // A NetworkAgent's allowedUids may need to be updated if the app has lost
+        // carrier config
+        for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+            if (nai.networkCapabilities.getAllowedUidsNoCopy().contains(uid)) {
+                final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
+                NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(
+                        nc,
+                        uid,
+                        false /* hasAutomotiveFeature (irrelevant) */,
+                        mDeps,
+                        mCarrierPrivilegeAuthenticator);
+                updateCapabilities(nai.getScore(), nai, nc);
+            }
+        }
+    }
+
     /**
      * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically:
      *
@@ -9499,7 +9586,6 @@
         final ArraySet<Integer> toAdd = new ArraySet<>(newUids);
         toRemove.removeAll(newUids);
         toAdd.removeAll(prevUids);
-
         try {
             if (!toAdd.isEmpty()) {
                 mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index 5705ebe..533278e 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -79,11 +79,16 @@
     @NonNull
     private final List<PrivilegeListener> mCarrierPrivilegesChangedListeners = new ArrayList<>();
     private final boolean mUseCallbacksForServiceChanged;
+    private final boolean mRequestRestrictedWifiEnabled;
+    @NonNull
+    private final CarrierPrivilegesLostListener mListener;
 
     public CarrierPrivilegeAuthenticator(@NonNull final Context c,
             @NonNull final Dependencies deps,
             @NonNull final TelephonyManager t,
-            @NonNull final TelephonyManagerShim telephonyManagerShim) {
+            @NonNull final TelephonyManagerShim telephonyManagerShim,
+            final boolean requestRestrictedWifiEnabled,
+            @NonNull CarrierPrivilegesLostListener listener) {
         mContext = c;
         mTelephonyManager = t;
         mTelephonyManagerShim = telephonyManagerShim;
@@ -92,6 +97,8 @@
         mHandler = new Handler(thread.getLooper());
         mUseCallbacksForServiceChanged = deps.isFeatureEnabled(
                 c, CARRIER_SERVICE_CHANGED_USE_CALLBACK);
+        mRequestRestrictedWifiEnabled = requestRestrictedWifiEnabled;
+        mListener = listener;
         final IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
         synchronized (mLock) {
@@ -113,8 +120,10 @@
     }
 
     public CarrierPrivilegeAuthenticator(@NonNull final Context c,
-            @NonNull final TelephonyManager t) {
-        this(c, new Dependencies(), t, TelephonyManagerShimImpl.newInstance(t));
+            @NonNull final TelephonyManager t, final boolean requestRestrictedWifiEnabled,
+            @NonNull CarrierPrivilegesLostListener listener) {
+        this(c, new Dependencies(), t, TelephonyManagerShimImpl.newInstance(t),
+                requestRestrictedWifiEnabled, listener);
     }
 
     public static class Dependencies {
@@ -133,6 +142,18 @@
         }
     }
 
+    /**
+     * Listener interface to get a notification when the carrier App lost its privileges.
+     */
+    public interface CarrierPrivilegesLostListener {
+        /**
+         * Called when the carrier App lost its privileges.
+         *
+         * @param uid  The uid of the carrier app which has lost its privileges.
+         */
+        void onCarrierPrivilegesLost(int uid);
+    }
+
     private void simConfigChanged() {
         synchronized (mLock) {
             unregisterCarrierPrivilegesListeners();
@@ -171,7 +192,11 @@
                 return;
             }
             synchronized (mLock) {
+                int oldUid = mCarrierServiceUid.get(mLogicalSlot);
                 mCarrierServiceUid.put(mLogicalSlot, carrierServiceUid);
+                if (oldUid != 0 && oldUid != carrierServiceUid) {
+                    mListener.onCarrierPrivilegesLost(oldUid);
+                }
             }
         }
     }
@@ -193,7 +218,11 @@
     private void unregisterCarrierPrivilegesListeners() {
         for (PrivilegeListener carrierPrivilegesListener : mCarrierPrivilegesChangedListeners) {
             removeCarrierPrivilegesListener(carrierPrivilegesListener);
+            int oldUid = mCarrierServiceUid.get(carrierPrivilegesListener.mLogicalSlot);
             mCarrierServiceUid.delete(carrierPrivilegesListener.mLogicalSlot);
+            if (oldUid != 0) {
+                mListener.onCarrierPrivilegesLost(oldUid);
+            }
         }
         mCarrierPrivilegesChangedListeners.clear();
     }
@@ -231,7 +260,7 @@
     public boolean isCarrierServiceUidForNetworkCapabilities(int callingUid,
             @NonNull NetworkCapabilities networkCapabilities) {
         if (callingUid == Process.INVALID_UID) return false;
-        final int subId;
+        int subId;
         if (networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_CELLULAR)) {
             subId = getSubIdFromTelephonySpecifier(networkCapabilities.getNetworkSpecifier());
         } else if (networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_WIFI)) {
@@ -239,6 +268,12 @@
         } else {
             subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                && mRequestRestrictedWifiEnabled
+                && networkCapabilities.getSubscriptionIds().size() == 1) {
+            subId = networkCapabilities.getSubscriptionIds().toArray(new Integer[0])[0];
+        }
+
         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
                 && !networkCapabilities.getSubscriptionIds().contains(subId)) {
             // Ideally, the code above should just use networkCapabilities.getSubscriptionIds()
@@ -257,10 +292,16 @@
     @VisibleForTesting
     void updateCarrierServiceUid() {
         synchronized (mLock) {
+            SparseIntArray oldCarrierServiceUid = mCarrierServiceUid.clone();
             mCarrierServiceUid.clear();
             for (int i = 0; i < mModemCount; i++) {
                 mCarrierServiceUid.put(i, getCarrierServicePackageUidForSlot(i));
             }
+            for (int i = 0; i < oldCarrierServiceUid.size(); i++) {
+                if (mCarrierServiceUid.indexOfValue(oldCarrierServiceUid.valueAt(i)) < 0) {
+                    mListener.onCarrierPrivilegesLost(oldCarrierServiceUid.valueAt(i));
+                }
+            }
         }
     }
 
@@ -340,6 +381,7 @@
 
     public void dump(IndentingPrintWriter pw) {
         pw.println("CarrierPrivilegeAuthenticator:");
+        pw.println("mRequestRestrictedWifiEnabled = " + mRequestRestrictedWifiEnabled);
         synchronized (mLock) {
             final int size = mCarrierServiceUid.size();
             for (int i = 0; i < size; ++i) {
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index f8f76ef..bf09160 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -36,6 +36,8 @@
     public static final String CARRIER_SERVICE_CHANGED_USE_CALLBACK =
             "carrier_service_changed_use_callback_version";
 
+    public static final String REQUEST_RESTRICTED_WIFI =
+            "request_restricted_wifi";
     private boolean mNoRematchAllRequestsOnRegister;
 
     /**
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 50cad45..76993a6 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -1551,7 +1551,7 @@
      * @param hasAutomotiveFeature true if this device has the automotive feature, false otherwise
      * @param authenticator the carrier privilege authenticator to check for telephony constraints
      */
-    public void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
+    public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
             final int creatorUid, final boolean hasAutomotiveFeature,
             @NonNull final ConnectivityService.Dependencies deps,
             @Nullable final CarrierPrivilegeAuthenticator authenticator) {
@@ -1564,7 +1564,7 @@
         }
     }
 
-    private boolean areAllowedUidsAcceptableFromNetworkAgent(
+    private static boolean areAllowedUidsAcceptableFromNetworkAgent(
             @NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
             @NonNull final ConnectivityService.Dependencies deps,
             @Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {