diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 415b3e5d24..ec169b608b 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -163,6 +163,8 @@ package android.net { public final class ProfileNetworkPreference implements android.os.Parcelable { method public int describeContents(); + method @NonNull public java.util.List getExcludedUids(); + method @NonNull public java.util.List getIncludedUids(); method public int getPreference(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; @@ -171,6 +173,8 @@ package android.net { public static final class ProfileNetworkPreference.Builder { ctor public ProfileNetworkPreference.Builder(); method @NonNull public android.net.ProfileNetworkPreference build(); + method @NonNull public android.net.ProfileNetworkPreference.Builder setExcludedUids(@Nullable java.util.List); + method @NonNull public android.net.ProfileNetworkPreference.Builder setIncludedUids(@Nullable java.util.List); method @NonNull public android.net.ProfileNetworkPreference.Builder setPreference(int); } diff --git a/framework/src/android/net/ProfileNetworkPreference.java b/framework/src/android/net/ProfileNetworkPreference.java index 2ce1698cf0..0571b367d1 100644 --- a/framework/src/android/net/ProfileNetworkPreference.java +++ b/framework/src/android/net/ProfileNetworkPreference.java @@ -20,11 +20,16 @@ import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.ConnectivityManager.ProfileNetworkPreferencePolicy; import android.os.Parcel; import android.os.Parcelable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + /** * Network preferences to be set for the user profile * {@link ProfileNetworkPreferencePolicy}. @@ -33,23 +38,69 @@ import android.os.Parcelable; @SystemApi(client = MODULE_LIBRARIES) public final class ProfileNetworkPreference implements Parcelable { private final @ProfileNetworkPreferencePolicy int mPreference; + private final List mIncludedUids; + private final List mExcludedUids; - private ProfileNetworkPreference(int preference) { + private ProfileNetworkPreference(int preference, List includedUids, + List excludedUids) { mPreference = preference; + if (includedUids != null) { + mIncludedUids = new ArrayList<>(includedUids); + } else { + mIncludedUids = new ArrayList<>(); + } + + if (excludedUids != null) { + mExcludedUids = new ArrayList<>(excludedUids); + } else { + mExcludedUids = new ArrayList<>(); + } } private ProfileNetworkPreference(Parcel in) { mPreference = in.readInt(); + mIncludedUids = in.readArrayList(Integer.class.getClassLoader()); + mExcludedUids = in.readArrayList(Integer.class.getClassLoader()); } public int getPreference() { return mPreference; } + /** + * Get the list of UIDs subject to this preference. + * + * Included UIDs and Excluded UIDs can't both be non-empty. + * if both are empty, it means this request applies to all uids in the user profile. + * if included is not empty, then only included UIDs are applied. + * if excluded is not empty, then it is all uids in the user profile except these UIDs. + * @return List of uids included for the profile preference. + * {@see #getExcludedUids()} + */ + public @NonNull List getIncludedUids() { + return new ArrayList<>(mIncludedUids); + } + + /** + * Get the list of UIDS excluded from this preference. + * + *
    Included UIDs and Excluded UIDs can't both be non-empty.
+ *
    If both are empty, it means this request applies to all uids in the user profile.
+ *
    If included is not empty, then only included UIDs are applied.
+ *
    If excluded is not empty, then it is all uids in the user profile except these UIDs.
+ * @return List of uids not included for the profile preference. + * {@see #getIncludedUids()} + */ + public @NonNull List getExcludedUids() { + return new ArrayList<>(mExcludedUids); + } + @Override public String toString() { return "ProfileNetworkPreference{" + "mPreference=" + getPreference() + + "mIncludedUids=" + mIncludedUids.toString() + + "mExcludedUids=" + mExcludedUids.toString() + '}'; } @@ -58,12 +109,16 @@ public final class ProfileNetworkPreference implements Parcelable { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final ProfileNetworkPreference that = (ProfileNetworkPreference) o; - return mPreference == that.mPreference; + return mPreference == that.mPreference + && (Objects.equals(mIncludedUids, that.mIncludedUids)) + && (Objects.equals(mExcludedUids, that.mExcludedUids)); } @Override public int hashCode() { - return mPreference; + return mPreference + + (Objects.hashCode(mIncludedUids) * 11) + + (Objects.hashCode(mExcludedUids) * 13); } /** @@ -73,6 +128,8 @@ public final class ProfileNetworkPreference implements Parcelable { public static final class Builder { private @ProfileNetworkPreferencePolicy int mPreference = PROFILE_NETWORK_PREFERENCE_DEFAULT; + private @NonNull List mIncludedUids = new ArrayList<>(); + private @NonNull List mExcludedUids = new ArrayList<>(); /** * Constructs an empty Builder with PROFILE_NETWORK_PREFERENCE_DEFAULT profile preference @@ -93,18 +150,66 @@ public final class ProfileNetworkPreference implements Parcelable { } /** + * This is a list of uids for which profile perefence is set. + * Null would mean that this preference applies to all uids in the profile. + * {@see #setExcludedUids(List)} + * Included UIDs and Excluded UIDs can't both be non-empty. + * if both are empty, it means this request applies to all uids in the user profile. + * if included is not empty, then only included UIDs are applied. + * if excluded is not empty, then it is all uids in the user profile except these UIDs. + * @param uids list of uids that are included + * @return The builder to facilitate chaining. + */ + @NonNull + public Builder setIncludedUids(@Nullable List uids) { + if (uids != null) { + mIncludedUids = new ArrayList(uids); + } else { + mIncludedUids = new ArrayList(); + } + return this; + } + + + /** + * This is a list of uids that are excluded for the profile perefence. + * {@see #setIncludedUids(List)} + * Included UIDs and Excluded UIDs can't both be non-empty. + * if both are empty, it means this request applies to all uids in the user profile. + * if included is not empty, then only included UIDs are applied. + * if excluded is not empty, then it is all uids in the user profile except these UIDs. + * @param uids list of uids that are not included + * @return The builder to facilitate chaining. + */ + @NonNull + public Builder setExcludedUids(@Nullable List uids) { + if (uids != null) { + mExcludedUids = new ArrayList(uids); + } else { + mExcludedUids = new ArrayList(); + } + return this; + } + + /** * Returns an instance of {@link ProfileNetworkPreference} created from the * fields set on this builder. */ @NonNull public ProfileNetworkPreference build() { - return new ProfileNetworkPreference(mPreference); + if (mIncludedUids.size() > 0 && mExcludedUids.size() > 0) { + throw new IllegalArgumentException("Both includedUids and excludedUids " + + "cannot be nonempty"); + } + return new ProfileNetworkPreference(mPreference, mIncludedUids, mExcludedUids); } } @Override public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { dest.writeInt(mPreference); + dest.writeList(mIncludedUids); + dest.writeList(mExcludedUids); } @Override diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index d625d1b853..6c27c4a33c 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.PermissionMonitor; 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 @@ public class ConnectivityService extends IConnectivityManager.Stub } 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 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 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 @@ public class ConnectivityService extends IConnectivityManager.Stub new Pair<>(preferenceList, listener))); } + private Set getUidListToBeAppliedForNetworkPreference( + @NonNull final UserHandle profile, + @NonNull final ProfileNetworkPreference profileNetworkPreference) { + final UidRange profileUids = UidRange.createForUser(profile); + Set 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 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(); + 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub return result; } + /** + * Compare if the given UID range sets have the same UIDs. + * + */ + private boolean isRangeAlreadyInPreferenceList( + @NonNull List preferenceList, + @NonNull Set 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 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 0000000000..7318296cc0 --- /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 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 removeRangeSetFromUidRange(@NonNull UidRange uids, + @NonNull ArraySet uidRangeSet) { + Objects.requireNonNull(uids); + Objects.requireNonNull(uidRangeSet); + final ArraySet filteredRangeSet = new ArraySet(); + 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 uidRangeSet1, + @NonNull Set 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 convertListToUidRange(@NonNull List uids) { + Objects.requireNonNull(uids); + final ArraySet uidRangeSet = new ArraySet(); + if (uids.size() == 0) { + return uidRangeSet; + } + List 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; + } +} diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index abb34dcce7..14ff981b4f 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -338,6 +338,7 @@ import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; +import com.android.server.connectivity.UidRangeUtils; import com.android.server.connectivity.Vpn; import com.android.server.connectivity.VpnProfileStore; import com.android.server.net.NetworkPinner; @@ -350,6 +351,7 @@ import com.android.testutils.TestableNetworkCallback; import com.android.testutils.TestableNetworkOfferCallback; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -443,6 +445,10 @@ public class ConnectivityServiceTest { private static final int TEST_WORK_PROFILE_USER_ID = 2; private static final int TEST_WORK_PROFILE_APP_UID = UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID); + private static final int TEST_APP_ID_2 = 104; + private static final int TEST_WORK_PROFILE_APP_UID_2 = + UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID_2); + private static final String CLAT_PREFIX = "v4-"; private static final String MOBILE_IFNAME = "test_rmnet_data0"; private static final String CLAT_MOBILE_IFNAME = CLAT_PREFIX + MOBILE_IFNAME; @@ -492,6 +498,8 @@ public class ConnectivityServiceTest { private TestNetworkCallback mSystemDefaultNetworkCallback; private TestNetworkCallback mProfileDefaultNetworkCallback; private TestNetworkCallback mTestPackageDefaultNetworkCallback; + private TestNetworkCallback mProfileDefaultNetworkCallbackAsAppUid2; + private TestNetworkCallback mTestPackageDefaultNetworkCallback2; // State variables required to emulate NetworkPolicyManagerService behaviour. private int mBlockedReasons = BLOCKED_REASON_NONE; @@ -12247,6 +12255,8 @@ public class ConnectivityServiceTest { private void registerDefaultNetworkCallbacks() { if (mSystemDefaultNetworkCallback != null || mDefaultNetworkCallback != null || mProfileDefaultNetworkCallback != null + || mProfileDefaultNetworkCallbackAsAppUid2 != null + || mTestPackageDefaultNetworkCallback2 != null || mTestPackageDefaultNetworkCallback != null) { throw new IllegalStateException("Default network callbacks already registered"); } @@ -12257,12 +12267,18 @@ public class ConnectivityServiceTest { mDefaultNetworkCallback = new TestNetworkCallback(); mProfileDefaultNetworkCallback = new TestNetworkCallback(); mTestPackageDefaultNetworkCallback = new TestNetworkCallback(); + mProfileDefaultNetworkCallbackAsAppUid2 = new TestNetworkCallback(); + mTestPackageDefaultNetworkCallback2 = new TestNetworkCallback(); mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback, new Handler(ConnectivityThread.getInstanceLooper())); mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback); registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback, TEST_WORK_PROFILE_APP_UID); registerDefaultNetworkCallbackAsUid(mTestPackageDefaultNetworkCallback, TEST_PACKAGE_UID); + registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallbackAsAppUid2, + TEST_WORK_PROFILE_APP_UID_2); + registerDefaultNetworkCallbackAsUid(mTestPackageDefaultNetworkCallback2, + TEST_PACKAGE_UID2); // TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well. mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED); } @@ -12280,6 +12296,12 @@ public class ConnectivityServiceTest { if (null != mTestPackageDefaultNetworkCallback) { mCm.unregisterNetworkCallback(mTestPackageDefaultNetworkCallback); } + if (null != mProfileDefaultNetworkCallbackAsAppUid2) { + mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallbackAsAppUid2); + } + if (null != mTestPackageDefaultNetworkCallback2) { + mCm.unregisterNetworkCallback(mTestPackageDefaultNetworkCallback2); + } } private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( @@ -13708,10 +13730,34 @@ public class ConnectivityServiceTest { } private UidRangeParcel[] uidRangeFor(final UserHandle handle) { - UidRange range = UidRange.createForUser(handle); + final UidRange range = UidRange.createForUser(handle); return new UidRangeParcel[] { new UidRangeParcel(range.start, range.stop) }; } + private UidRangeParcel[] uidRangeFor(final UserHandle handle, + ProfileNetworkPreference profileNetworkPreference) { + final Set uidRangeSet; + UidRange range = UidRange.createForUser(handle); + if (profileNetworkPreference.getIncludedUids().size() != 0) { + uidRangeSet = UidRangeUtils.convertListToUidRange( + profileNetworkPreference.getIncludedUids()); + } else if (profileNetworkPreference.getExcludedUids().size() != 0) { + uidRangeSet = UidRangeUtils.removeRangeSetFromUidRange( + range, UidRangeUtils.convertListToUidRange( + profileNetworkPreference.getExcludedUids())); + } else { + uidRangeSet = new ArraySet<>(); + uidRangeSet.add(range); + } + UidRangeParcel[] uidRangeParcels = new UidRangeParcel[uidRangeSet.size()]; + int i = 0; + for (UidRange range1 : uidRangeSet) { + uidRangeParcels[i] = new UidRangeParcel(range1.start, range1.stop); + i++; + } + return uidRangeParcels; + } + private static class TestOnCompleteListener implements Runnable { final class OnComplete {} final ArrayTrackRecord.ReadHead mHistory = @@ -13762,17 +13808,22 @@ public class ConnectivityServiceTest { */ public void testPreferenceForUserNetworkUpDownForGivenPreference( ProfileNetworkPreference profileNetworkPreference, - boolean connectWorkProfileAgentAhead) throws Exception { + boolean connectWorkProfileAgentAhead, + UserHandle testHandle, + TestNetworkCallback profileDefaultNetworkCallback, + TestNetworkCallback disAllowProfileDefaultNetworkCallback) throws Exception { final InOrder inOrder = inOrder(mMockNetd); - final UserHandle testHandle = setupEnterpriseNetwork(); - registerDefaultNetworkCallbacks(); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + profileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + if (disAllowProfileDefaultNetworkCallback != null) { + disAllowProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks( + mCellNetworkAgent); + } inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); @@ -13800,33 +13851,43 @@ public class ConnectivityServiceTest { // system default is not handled specially, the rules are always active as long as // a preference is set. inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( - mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle), + mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle, profileNetworkPreference), PREFERENCE_ORDER_PROFILE)); } // The enterprise network is not ready yet. assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); if (allowFallback) { - assertNoCallbacks(mProfileDefaultNetworkCallback); + assertNoCallbacks(profileDefaultNetworkCallback); } else if (!connectWorkProfileAgentAhead) { - mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + if (disAllowProfileDefaultNetworkCallback != null) { + assertNoCallbacks(disAllowProfileDefaultNetworkCallback); + } } if (!connectWorkProfileAgentAhead) { workAgent.connect(false); } - mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent); + profileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent); + if (disAllowProfileDefaultNetworkCallback != null) { + disAllowProfileDefaultNetworkCallback.assertNoCallback(); + } mSystemDefaultNetworkCallback.assertNoCallback(); mDefaultNetworkCallback.assertNoCallback(); inOrder.verify(mMockNetd).networkCreate( nativeNetworkConfigPhysical(workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM)); inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( - workAgent.getNetwork().netId, uidRangeFor(testHandle), PREFERENCE_ORDER_PROFILE)); + workAgent.getNetwork().netId, + uidRangeFor(testHandle, profileNetworkPreference), + PREFERENCE_ORDER_PROFILE)); if (allowFallback) { inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig( - mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle), + mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle, profileNetworkPreference), PREFERENCE_ORDER_PROFILE)); } @@ -13834,14 +13895,20 @@ public class ConnectivityServiceTest { // not to the other apps. workAgent.setNetworkValid(true /* isStrictMode */); workAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); - mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, + profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED) && nc.hasCapability(NET_CAPABILITY_ENTERPRISE)); + if (disAllowProfileDefaultNetworkCallback != null) { + assertNoCallbacks(disAllowProfileDefaultNetworkCallback); + } assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); workAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); - mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc -> + profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc -> nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + if (disAllowProfileDefaultNetworkCallback != null) { + assertNoCallbacks(disAllowProfileDefaultNetworkCallback); + } assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); // Conversely, change a capability on the system-wide default network and make sure @@ -13851,7 +13918,11 @@ public class ConnectivityServiceTest { nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); - mProfileDefaultNetworkCallback.assertNoCallback(); + if (disAllowProfileDefaultNetworkCallback != null) { + disAllowProfileDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + } + profileDefaultNetworkCallback.assertNoCallback(); // Disconnect and reconnect the system-wide default network and make sure that the // apps on this network see the appropriate callbacks, and the app on the work profile @@ -13859,28 +13930,41 @@ public class ConnectivityServiceTest { mCellNetworkAgent.disconnect(); mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); - mProfileDefaultNetworkCallback.assertNoCallback(); + if (disAllowProfileDefaultNetworkCallback != null) { + disAllowProfileDefaultNetworkCallback.expectCallback( + CallbackEntry.LOST, mCellNetworkAgent); + } + profileDefaultNetworkCallback.assertNoCallback(); inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - mProfileDefaultNetworkCallback.assertNoCallback(); + if (disAllowProfileDefaultNetworkCallback != null) { + disAllowProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks( + mCellNetworkAgent); + + } + profileDefaultNetworkCallback.assertNoCallback(); inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); // When the agent disconnects, test that the app on the work profile falls back to the // default network. workAgent.disconnect(); - mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent); + profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent); if (allowFallback) { - mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + profileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + if (disAllowProfileDefaultNetworkCallback != null) { + assertNoCallbacks(disAllowProfileDefaultNetworkCallback); + } } assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); if (allowFallback) { inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( - mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle), + mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle, profileNetworkPreference), PREFERENCE_ORDER_PROFILE)); } inOrder.verify(mMockNetd).networkDestroy(workAgent.getNetwork().netId); @@ -13888,8 +13972,12 @@ public class ConnectivityServiceTest { mCellNetworkAgent.disconnect(); mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + if (disAllowProfileDefaultNetworkCallback != null) { + disAllowProfileDefaultNetworkCallback.expectCallback( + CallbackEntry.LOST, mCellNetworkAgent); + } if (allowFallback) { - mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); } // Waiting for the handler to be idle before checking for networkDestroy is necessary @@ -13903,30 +13991,40 @@ public class ConnectivityServiceTest { final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(); workAgent2.connect(false); - mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2); + profileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2); + if (disAllowProfileDefaultNetworkCallback != null) { + assertNoCallbacks(disAllowProfileDefaultNetworkCallback); + } assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM)); inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( - workAgent2.getNetwork().netId, uidRangeFor(testHandle), PREFERENCE_ORDER_PROFILE)); + workAgent2.getNetwork().netId, + uidRangeFor(testHandle, profileNetworkPreference), PREFERENCE_ORDER_PROFILE)); workAgent2.setNetworkValid(true /* isStrictMode */); workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid()); - mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2, + profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2, nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE) && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + if (disAllowProfileDefaultNetworkCallback != null) { + assertNoCallbacks(disAllowProfileDefaultNetworkCallback); + } assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); inOrder.verify(mMockNetd, never()).networkAddUidRangesParcel(any()); // When the agent disconnects, test that the app on the work profile fall back to the // default network. workAgent2.disconnect(); - mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2); + profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2); + if (disAllowProfileDefaultNetworkCallback != null) { + assertNoCallbacks(disAllowProfileDefaultNetworkCallback); + } assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); inOrder.verify(mMockNetd).networkDestroy(workAgent2.getNetwork().netId); assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, - mProfileDefaultNetworkCallback); + profileDefaultNetworkCallback); // Callbacks will be unregistered by tearDown() } @@ -13938,11 +14036,14 @@ public class ConnectivityServiceTest { */ @Test public void testPreferenceForUserNetworkUpDown() throws Exception { + final UserHandle testHandle = setupEnterpriseNetwork(); ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder = new ProfileNetworkPreference.Builder(); profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + registerDefaultNetworkCallbacks(); testPreferenceForUserNetworkUpDownForGivenPreference( - profileNetworkPreferenceBuilder.build(), false); + profileNetworkPreferenceBuilder.build(), false, + testHandle, mProfileDefaultNetworkCallback, null); } /** @@ -13952,12 +14053,15 @@ public class ConnectivityServiceTest { */ @Test public void testPreferenceForUserNetworkUpDownWithNoFallback() throws Exception { + final UserHandle testHandle = setupEnterpriseNetwork(); ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder = new ProfileNetworkPreference.Builder(); profileNetworkPreferenceBuilder.setPreference( PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK); + registerDefaultNetworkCallbacks(); testPreferenceForUserNetworkUpDownForGivenPreference( - profileNetworkPreferenceBuilder.build(), false); + profileNetworkPreferenceBuilder.build(), false, + testHandle, mProfileDefaultNetworkCallback, null); } /** @@ -13969,12 +14073,143 @@ public class ConnectivityServiceTest { @Test public void testPreferenceForUserNetworkUpDownWithNoFallbackWithAlreadyConnectedWorkAgent() throws Exception { + final UserHandle testHandle = setupEnterpriseNetwork(); ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder = new ProfileNetworkPreference.Builder(); profileNetworkPreferenceBuilder.setPreference( PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK); + registerDefaultNetworkCallbacks(); testPreferenceForUserNetworkUpDownForGivenPreference( - profileNetworkPreferenceBuilder.build(), true); + profileNetworkPreferenceBuilder.build(), true, testHandle, + mProfileDefaultNetworkCallback, null); + } + + /** + * Make sure per-profile networking preference for specific uid of test handle + * behaves as expected + */ + @Test + public void testPreferenceForDefaultUidOfTestHandle() throws Exception { + final UserHandle testHandle = setupEnterpriseNetwork(); + ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder = + new ProfileNetworkPreference.Builder(); + profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + profileNetworkPreferenceBuilder.setIncludedUids( + List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID))); + registerDefaultNetworkCallbacks(); + testPreferenceForUserNetworkUpDownForGivenPreference( + profileNetworkPreferenceBuilder.build(), false, testHandle, + mProfileDefaultNetworkCallback, null); + } + + /** + * Make sure per-profile networking preference for specific uid of test handle + * behaves as expected + */ + @Test + public void testPreferenceForSpecificUidOfOnlyOneApp() throws Exception { + final UserHandle testHandle = setupEnterpriseNetwork(); + ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder = + new ProfileNetworkPreference.Builder(); + profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + profileNetworkPreferenceBuilder.setIncludedUids( + List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2))); + registerDefaultNetworkCallbacks(); + testPreferenceForUserNetworkUpDownForGivenPreference( + profileNetworkPreferenceBuilder.build(), false, + testHandle, mProfileDefaultNetworkCallbackAsAppUid2, null); + } + + /** + * Make sure per-profile networking preference for specific uid of test handle + * behaves as expected + */ + @Test + public void testPreferenceForDisallowSpecificUidOfApp() throws Exception { + final UserHandle testHandle = setupEnterpriseNetwork(); + ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder = + new ProfileNetworkPreference.Builder(); + profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + profileNetworkPreferenceBuilder.setExcludedUids( + List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2))); + registerDefaultNetworkCallbacks(); + testPreferenceForUserNetworkUpDownForGivenPreference( + profileNetworkPreferenceBuilder.build(), false, + testHandle, mProfileDefaultNetworkCallback, + mProfileDefaultNetworkCallbackAsAppUid2); + } + + /** + * Make sure per-profile networking preference for specific uid of test handle + * invalid uid inputs + */ + @Test + public void testPreferenceForInvalidUids() throws Exception { + final UserHandle testHandle = setupEnterpriseNetwork(); + ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder = + new ProfileNetworkPreference.Builder(); + profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + profileNetworkPreferenceBuilder.setExcludedUids( + List.of(testHandle.getUid(0) - 1)); + final TestOnCompleteListener listener = new TestOnCompleteListener(); + Assert.assertThrows(IllegalArgumentException.class, () -> mCm.setProfileNetworkPreferences( + testHandle, List.of(profileNetworkPreferenceBuilder.build()), + r -> r.run(), listener)); + + profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + profileNetworkPreferenceBuilder.setIncludedUids( + List.of(testHandle.getUid(0) - 1)); + Assert.assertThrows(IllegalArgumentException.class, + () -> mCm.setProfileNetworkPreferences( + testHandle, List.of(profileNetworkPreferenceBuilder.build()), + r -> r.run(), listener)); + + + profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + profileNetworkPreferenceBuilder.setIncludedUids( + List.of(testHandle.getUid(0) - 1)); + profileNetworkPreferenceBuilder.setExcludedUids( + List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2))); + Assert.assertThrows(IllegalArgumentException.class, + () -> mCm.setProfileNetworkPreferences( + testHandle, List.of(profileNetworkPreferenceBuilder.build()), + r -> r.run(), listener)); + + ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 = + new ProfileNetworkPreference.Builder(); + profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + profileNetworkPreferenceBuilder2.setIncludedUids( + List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2))); + profileNetworkPreferenceBuilder.setIncludedUids( + List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2))); + Assert.assertThrows(IllegalArgumentException.class, + () -> mCm.setProfileNetworkPreferences( + testHandle, List.of(profileNetworkPreferenceBuilder.build(), + profileNetworkPreferenceBuilder2.build()), + r -> r.run(), listener)); + + profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + profileNetworkPreferenceBuilder2.setExcludedUids( + List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2))); + profileNetworkPreferenceBuilder.setExcludedUids( + List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2))); + Assert.assertThrows(IllegalArgumentException.class, + () -> mCm.setProfileNetworkPreferences( + testHandle, List.of(profileNetworkPreferenceBuilder.build(), + profileNetworkPreferenceBuilder2.build()), + r -> r.run(), listener)); + + profileNetworkPreferenceBuilder2.setPreference( + PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK); + profileNetworkPreferenceBuilder2.setExcludedUids( + List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2))); + profileNetworkPreferenceBuilder.setExcludedUids( + List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2))); + Assert.assertThrows(IllegalArgumentException.class, + () -> mCm.setProfileNetworkPreferences( + testHandle, List.of(profileNetworkPreferenceBuilder.build(), + profileNetworkPreferenceBuilder2.build()), + r -> r.run(), listener)); } /** diff --git a/tests/unit/java/com/android/server/connectivity/UidRangeUtilsTest.java b/tests/unit/java/com/android/server/connectivity/UidRangeUtilsTest.java new file mode 100644 index 0000000000..b8c267395b --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/UidRangeUtilsTest.java @@ -0,0 +1,354 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.UidRange; +import android.os.Build; +import android.util.ArraySet; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Tests for UidRangeUtils. + * + * Build, install and run with: + * runtest frameworks-net -c com.android.server.connectivity.UidRangeUtilsTest + */ +@RunWith(DevSdkIgnoreRunner.class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) +public class UidRangeUtilsTest { + private static void assertInSameRange(@NonNull final String msg, + @Nullable final UidRange r1, + @Nullable final Set s2) { + assertTrue(msg + " : " + s2 + " unexpectedly is not in range of " + r1, + UidRangeUtils.isRangeSetInUidRange(r1, s2)); + } + + private static void assertNotInSameRange(@NonNull final String msg, + @Nullable final UidRange r1, @Nullable final Set s2) { + assertFalse(msg + " : " + s2 + " unexpectedly is in range of " + r1, + UidRangeUtils.isRangeSetInUidRange(r1, s2)); + } + + @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) + public void testRangeSetInUidRange() { + final UidRange uids1 = new UidRange(1, 100); + final UidRange uids2 = new UidRange(3, 300); + final UidRange uids3 = new UidRange(1, 1000); + final UidRange uids4 = new UidRange(1, 100); + final UidRange uids5 = new UidRange(2, 20); + final UidRange uids6 = new UidRange(3, 30); + + assertThrows(NullPointerException.class, + () -> UidRangeUtils.isRangeSetInUidRange(null, null)); + assertThrows(NullPointerException.class, + () -> UidRangeUtils.isRangeSetInUidRange(uids1, null)); + + final ArraySet set1 = new ArraySet<>(); + final ArraySet set2 = new ArraySet<>(); + + assertThrows(NullPointerException.class, + () -> UidRangeUtils.isRangeSetInUidRange(null, set1)); + assertInSameRange("uids1 <=> empty", uids1, set2); + + set2.add(uids1); + assertInSameRange("uids1 <=> uids1", uids1, set2); + + set2.clear(); + set2.add(uids2); + assertNotInSameRange("uids1 <=> uids2", uids1, set2); + set2.clear(); + set2.add(uids3); + assertNotInSameRange("uids1 <=> uids3", uids1, set2); + set2.clear(); + set2.add(uids4); + assertInSameRange("uids1 <=> uids4", uids1, set2); + + set2.clear(); + set2.add(uids5); + set2.add(uids6); + assertInSameRange("uids1 <=> uids5, 6", uids1, set2); + + set2.clear(); + set2.add(uids2); + set2.add(uids6); + assertNotInSameRange("uids1 <=> uids2, 6", uids1, set2); + } + + @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) + public void testRemoveRangeSetFromUidRange() { + final UidRange uids1 = new UidRange(1, 100); + final UidRange uids2 = new UidRange(3, 300); + final UidRange uids3 = new UidRange(1, 1000); + final UidRange uids4 = new UidRange(1, 100); + final UidRange uids5 = new UidRange(2, 20); + final UidRange uids6 = new UidRange(3, 30); + final UidRange uids7 = new UidRange(30, 39); + + final UidRange uids8 = new UidRange(1, 1); + final UidRange uids9 = new UidRange(21, 100); + final UidRange uids10 = new UidRange(1, 2); + final UidRange uids11 = new UidRange(31, 100); + + final UidRange uids12 = new UidRange(1, 1); + final UidRange uids13 = new UidRange(21, 29); + final UidRange uids14 = new UidRange(40, 100); + + final UidRange uids15 = new UidRange(3, 30); + final UidRange uids16 = new UidRange(31, 39); + + assertThrows(NullPointerException.class, + () -> UidRangeUtils.removeRangeSetFromUidRange(null, null)); + Set expected = new ArraySet<>(); + expected.add(uids1); + assertThrows(NullPointerException.class, + () -> UidRangeUtils.removeRangeSetFromUidRange(uids1, null)); + assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, new ArraySet<>())); + + expected.clear(); + final ArraySet set2 = new ArraySet<>(); + set2.add(uids1); + assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2)); + set2.clear(); + set2.add(uids4); + assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2)); + + expected.add(uids10); + set2.clear(); + set2.add(uids2); + assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2)); + + expected.clear(); + set2.clear(); + set2.add(uids3); + assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2)); + + set2.clear(); + set2.add(uids3); + set2.add(uids6); + assertThrows(IllegalArgumentException.class, + () -> UidRangeUtils.removeRangeSetFromUidRange(uids1, set2)); + + expected.clear(); + expected.add(uids8); + expected.add(uids9); + set2.clear(); + set2.add(uids5); + assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2)); + + expected.clear(); + expected.add(uids10); + expected.add(uids11); + set2.clear(); + set2.add(uids6); + assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2)); + + expected.clear(); + expected.add(uids12); + expected.add(uids13); + expected.add(uids14); + set2.clear(); + set2.add(uids5); + set2.add(uids7); + assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2)); + + expected.clear(); + expected.add(uids10); + expected.add(uids14); + set2.clear(); + set2.add(uids15); + set2.add(uids16); + assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2)); + } + + private static void assertRangeOverlaps(@NonNull final String msg, + @Nullable final Set s1, + @Nullable final Set s2) { + assertTrue(msg + " : " + s2 + " unexpectedly does not overlap with " + s1, + UidRangeUtils.doesRangeSetOverlap(s1, s2)); + } + + private static void assertRangeDoesNotOverlap(@NonNull final String msg, + @Nullable final Set s1, @Nullable final Set s2) { + assertFalse(msg + " : " + s2 + " unexpectedly ovelaps with " + s1, + UidRangeUtils.doesRangeSetOverlap(s1, s2)); + } + + @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) + public void testRangeSetOverlap() { + final UidRange uids1 = new UidRange(1, 100); + final UidRange uids2 = new UidRange(3, 300); + final UidRange uids3 = new UidRange(1, 1000); + final UidRange uids4 = new UidRange(1, 100); + final UidRange uids5 = new UidRange(2, 20); + final UidRange uids6 = new UidRange(3, 30); + final UidRange uids7 = new UidRange(0, 0); + final UidRange uids8 = new UidRange(1, 500); + final UidRange uids9 = new UidRange(101, 200); + + assertThrows(NullPointerException.class, + () -> UidRangeUtils.doesRangeSetOverlap(null, null)); + + final ArraySet set1 = new ArraySet<>(); + final ArraySet set2 = new ArraySet<>(); + assertThrows(NullPointerException.class, + () -> UidRangeUtils.doesRangeSetOverlap(set1, null)); + assertThrows(NullPointerException.class, + () -> UidRangeUtils.doesRangeSetOverlap(null, set2)); + assertRangeDoesNotOverlap("empty <=> null", set1, set2); + + set2.add(uids1); + set1.add(uids1); + assertRangeOverlaps("uids1 <=> uids1", set1, set2); + + set1.clear(); + set1.add(uids1); + set2.clear(); + set2.add(uids2); + assertRangeOverlaps("uids1 <=> uids2", set1, set2); + + set1.clear(); + set1.add(uids1); + set2.clear(); + set2.add(uids3); + assertRangeOverlaps("uids1 <=> uids3", set1, set2); + + set1.clear(); + set1.add(uids1); + set2.clear(); + set2.add(uids4); + assertRangeOverlaps("uids1 <=> uids4", set1, set2); + + set1.clear(); + set1.add(uids1); + set2.clear(); + set2.add(uids5); + set2.add(uids6); + assertRangeOverlaps("uids1 <=> uids5,6", set1, set2); + + set1.clear(); + set1.add(uids1); + set2.clear(); + set2.add(uids7); + assertRangeDoesNotOverlap("uids1 <=> uids7", set1, set2); + + set1.clear(); + set1.add(uids1); + set2.clear(); + set2.add(uids9); + assertRangeDoesNotOverlap("uids1 <=> uids9", set1, set2); + + set1.clear(); + set1.add(uids1); + set2.clear(); + set2.add(uids8); + assertRangeOverlaps("uids1 <=> uids8", set1, set2); + + + set1.clear(); + set1.add(uids1); + set2.clear(); + set2.add(uids8); + set2.add(uids7); + assertRangeOverlaps("uids1 <=> uids7, 8", set1, set2); + } + + @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) + public void testConvertListToUidRange() { + final UidRange uids1 = new UidRange(1, 1); + final UidRange uids2 = new UidRange(1, 2); + final UidRange uids3 = new UidRange(100, 100); + final UidRange uids4 = new UidRange(10, 10); + + final UidRange uids5 = new UidRange(10, 14); + final UidRange uids6 = new UidRange(20, 24); + + final Set expected = new ArraySet<>(); + final List input = new ArrayList(); + + assertThrows(NullPointerException.class, () -> UidRangeUtils.convertListToUidRange(null)); + assertEquals(expected, UidRangeUtils.convertListToUidRange(input)); + + input.add(1); + expected.add(uids1); + assertEquals(expected, UidRangeUtils.convertListToUidRange(input)); + + input.add(2); + expected.clear(); + expected.add(uids2); + assertEquals(expected, UidRangeUtils.convertListToUidRange(input)); + + input.clear(); + input.add(1); + input.add(100); + expected.clear(); + expected.add(uids1); + expected.add(uids3); + assertEquals(expected, UidRangeUtils.convertListToUidRange(input)); + + input.clear(); + input.add(100); + input.add(1); + expected.clear(); + expected.add(uids1); + expected.add(uids3); + assertEquals(expected, UidRangeUtils.convertListToUidRange(input)); + + input.clear(); + input.add(100); + input.add(1); + input.add(2); + input.add(1); + input.add(10); + expected.clear(); + expected.add(uids2); + expected.add(uids4); + expected.add(uids3); + assertEquals(expected, UidRangeUtils.convertListToUidRange(input)); + + input.clear(); + input.add(10); + input.add(11); + input.add(12); + input.add(13); + input.add(14); + input.add(20); + input.add(21); + input.add(22); + input.add(23); + input.add(24); + expected.clear(); + expected.add(uids5); + expected.add(uids6); + assertEquals(expected, UidRangeUtils.convertListToUidRange(input)); + } +}