Ability to specify which all applications fall under enterprise slice.

Bug: 194332512
Test: unit test
Change-Id: I94549a41aaa717add22b0a3e5035beacf6f1b8f2
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index d625d1b..6c27c4a 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -262,6 +262,7 @@
 import com.android.server.connectivity.ProfileNetworkPreferenceList;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.QosCallbackTracker;
+import com.android.server.connectivity.UidRangeUtils;
 
 import libcore.io.IoUtils;
 
@@ -1489,16 +1490,17 @@
     }
 
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
-        return createDefaultNetworkCapabilitiesForUidRange(new UidRange(uid, uid));
+        return createDefaultNetworkCapabilitiesForUidRangeSet(Collections.singleton(
+                new UidRange(uid, uid)));
     }
 
-    private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRange(
-            @NonNull final UidRange uids) {
+    private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRangeSet(
+            @NonNull final Set<UidRange> uidRangeSet) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
         netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
-        netCap.setUids(UidRange.toIntRanges(Collections.singleton(uids)));
+        netCap.setUids(UidRange.toIntRanges(uidRangeSet));
         return netCap;
     }
 
@@ -10150,8 +10152,14 @@
                     allowFallback = false;
                     // continue to process the enterprise preference.
                 case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
-                    final UidRange uids = UidRange.createForUser(profile);
-                    nc = createDefaultNetworkCapabilitiesForUidRange(uids);
+                    final Set<UidRange> uidRangeSet =
+                            getUidListToBeAppliedForNetworkPreference(profile, preference);
+                    if (!isRangeAlreadyInPreferenceList(preferenceList, uidRangeSet)) {
+                        nc = createDefaultNetworkCapabilitiesForUidRangeSet(uidRangeSet);
+                    } else {
+                        throw new IllegalArgumentException(
+                                "Overlapping uid range in setProfileNetworkPreferences");
+                    }
                     nc.addCapability(NET_CAPABILITY_ENTERPRISE);
                     nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
                     break;
@@ -10166,6 +10174,35 @@
                 new Pair<>(preferenceList, listener)));
     }
 
+    private Set<UidRange> getUidListToBeAppliedForNetworkPreference(
+            @NonNull final UserHandle profile,
+            @NonNull final ProfileNetworkPreference profileNetworkPreference) {
+        final UidRange profileUids = UidRange.createForUser(profile);
+        Set<UidRange> uidRangeSet = UidRangeUtils.convertListToUidRange(
+                profileNetworkPreference.getIncludedUids());
+        if (uidRangeSet.size() > 0) {
+            if (!UidRangeUtils.isRangeSetInUidRange(profileUids, uidRangeSet)) {
+                throw new IllegalArgumentException(
+                        "Allow uid range is outside the uid range of profile.");
+            }
+        } else {
+            ArraySet<UidRange> disallowUidRangeSet = UidRangeUtils.convertListToUidRange(
+                    profileNetworkPreference.getExcludedUids());
+            if (disallowUidRangeSet.size() > 0) {
+                if (!UidRangeUtils.isRangeSetInUidRange(profileUids, disallowUidRangeSet)) {
+                    throw new IllegalArgumentException(
+                            "disallow uid range is outside the uid range of profile.");
+                }
+                uidRangeSet = UidRangeUtils.removeRangeSetFromUidRange(profileUids,
+                        disallowUidRangeSet);
+            } else {
+                uidRangeSet = new ArraySet<UidRange>();
+                uidRangeSet.add(profileUids);
+            }
+        }
+        return uidRangeSet;
+    }
+
     private void validateNetworkCapabilitiesOfProfileNetworkPreference(
             @Nullable final NetworkCapabilities nc) {
         if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
@@ -10187,6 +10224,11 @@
                 nrs.add(createDefaultInternetRequestForTransport(
                         TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
             }
+            if (VDBG) {
+                loge("pref.capabilities.getUids():" + UidRange.fromIntRanges(
+                        pref.capabilities.getUids()));
+            }
+
             setNetworkRequestUids(nrs, UidRange.fromIntRanges(pref.capabilities.getUids()));
             final NetworkRequestInfo nri = new NetworkRequestInfo(Process.myUid(), nrs,
                     PREFERENCE_ORDER_PROFILE);
@@ -10195,6 +10237,25 @@
         return result;
     }
 
+    /**
+     * Compare if the given UID range sets have the same UIDs.
+     *
+     */
+    private boolean isRangeAlreadyInPreferenceList(
+            @NonNull List<ProfileNetworkPreferenceList.Preference> preferenceList,
+            @NonNull Set<UidRange> uidRangeSet) {
+        if (uidRangeSet.size() == 0 || preferenceList.size() == 0) {
+            return false;
+        }
+        for (ProfileNetworkPreferenceList.Preference pref : preferenceList) {
+            if (UidRangeUtils.doesRangeSetOverlap(
+                    UidRange.fromIntRanges(pref.capabilities.getUids()), uidRangeSet)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void handleSetProfileNetworkPreference(
             @NonNull final List<ProfileNetworkPreferenceList.Preference> preferenceList,
             @Nullable final IOnCompleteListener listener) {
diff --git a/service/src/com/android/server/connectivity/UidRangeUtils.java b/service/src/com/android/server/connectivity/UidRangeUtils.java
new file mode 100644
index 0000000..7318296
--- /dev/null
+++ b/service/src/com/android/server/connectivity/UidRangeUtils.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.annotation.NonNull;
+import android.net.UidRange;
+import android.util.ArraySet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Utility class for UidRange
+ *
+ * @hide
+ */
+public final class UidRangeUtils {
+    /**
+     * Check if given uid range set is within the uid range
+     * @param uids uid range in which uidRangeSet is checked to be in range.
+     * @param uidRangeSet uid range set to be be checked if it is in range of uids
+     * @return true uidRangeSet is in the range of uids
+     * @hide
+     */
+    public static boolean isRangeSetInUidRange(@NonNull UidRange uids,
+            @NonNull Set<UidRange> uidRangeSet) {
+        Objects.requireNonNull(uids);
+        Objects.requireNonNull(uidRangeSet);
+        if (uidRangeSet.size() == 0) {
+            return true;
+        }
+        for (UidRange range : uidRangeSet) {
+            if (!uids.contains(range.start) || !uids.contains(range.stop)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Remove given uid ranges set from a uid range
+     * @param uids uid range from which uidRangeSet will be removed
+     * @param uidRangeSet uid range set to be removed from uids.
+     * WARNING : This function requires the UidRanges in uidRangeSet to be disjoint
+     * WARNING : This function requires the arrayset to be iterated in increasing order of the
+     *                    ranges. Today this is provided by the iteration order stability of
+     *                    ArraySet, and the fact that the code creating this ArraySet always
+     *                    creates it in increasing order.
+     * Note : if any of the above is not satisfied this function throws IllegalArgumentException
+     * TODO : remove these limitations
+     * @hide
+     */
+    public static ArraySet<UidRange> removeRangeSetFromUidRange(@NonNull UidRange uids,
+            @NonNull ArraySet<UidRange> uidRangeSet) {
+        Objects.requireNonNull(uids);
+        Objects.requireNonNull(uidRangeSet);
+        final ArraySet<UidRange> filteredRangeSet = new ArraySet<UidRange>();
+        if (uidRangeSet.size() == 0) {
+            filteredRangeSet.add(uids);
+            return filteredRangeSet;
+        }
+
+        int start = uids.start;
+        UidRange previousRange = null;
+        for (UidRange uidRange : uidRangeSet) {
+            if (previousRange != null) {
+                if (previousRange.stop > uidRange.start) {
+                    throw new IllegalArgumentException("UID ranges are not increasing order");
+                }
+            }
+            if (uidRange.start > start) {
+                filteredRangeSet.add(new UidRange(start, uidRange.start - 1));
+                start = uidRange.stop + 1;
+            } else if (uidRange.start == start) {
+                start = uidRange.stop + 1;
+            }
+            previousRange = uidRange;
+        }
+        if (start < uids.stop) {
+            filteredRangeSet.add(new UidRange(start, uids.stop));
+        }
+        return filteredRangeSet;
+    }
+
+    /**
+     * Compare if the given UID range sets have overlapping uids
+     * @param uidRangeSet1 first uid range set to check for overlap
+     * @param uidRangeSet2 second uid range set to check for overlap
+     * @hide
+     */
+    public static boolean doesRangeSetOverlap(@NonNull Set<UidRange> uidRangeSet1,
+            @NonNull Set<UidRange> uidRangeSet2) {
+        Objects.requireNonNull(uidRangeSet1);
+        Objects.requireNonNull(uidRangeSet2);
+
+        if (uidRangeSet1.size() == 0 || uidRangeSet2.size() == 0) {
+            return false;
+        }
+        for (UidRange range1 : uidRangeSet1) {
+            for (UidRange range2 : uidRangeSet2) {
+                if (range1.contains(range2.start) || range1.contains(range2.stop)
+                        || range2.contains(range1.start) || range2.contains(range1.stop)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Convert a list of uid to set of UidRanges.
+     * @param uids list of uids
+     * @return set of UidRanges
+     * @hide
+     */
+    public static ArraySet<UidRange> convertListToUidRange(@NonNull List<Integer> uids) {
+        Objects.requireNonNull(uids);
+        final ArraySet<UidRange> uidRangeSet = new ArraySet<UidRange>();
+        if (uids.size() == 0) {
+            return uidRangeSet;
+        }
+        List<Integer> uidsNew = new ArrayList<>(uids);
+        Collections.sort(uidsNew);
+        int start = uidsNew.get(0);
+        int stop = start;
+
+        for (Integer i : uidsNew) {
+            if (i <= stop + 1) {
+                stop = i;
+            } else {
+                uidRangeSet.add(new UidRange(start, stop));
+                start = i;
+                stop = i;
+            }
+        }
+        uidRangeSet.add(new UidRange(start, stop));
+        return uidRangeSet;
+    }
+}