diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index d1d51da151..cfab8724fd 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -279,6 +279,7 @@ package android.net { method @Nullable public String getSsid(); method @NonNull public java.util.Set getSubscriptionIds(); method @NonNull public int[] getTransportTypes(); + method @Nullable public java.util.List getUnderlyingNetworks(); method public boolean isPrivateDnsBroken(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); field public static final int NET_CAPABILITY_BIP = 31; // 0x1f @@ -309,6 +310,7 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set); method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); + method @NonNull public android.net.NetworkCapabilities.Builder setUnderlyingNetworks(@Nullable java.util.List); method @NonNull public static android.net.NetworkCapabilities.Builder withoutDefaultCapabilities(); } diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index ae5cfda76d..e9bcd95a5e 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -42,7 +42,10 @@ import com.android.net.module.util.NetworkCapabilitiesUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.StringJoiner; @@ -129,6 +132,11 @@ public final class NetworkCapabilities implements Parcelable { // Set to true when private DNS is broken. private boolean mPrivateDnsBroken; + // Underlying networks, if any. VPNs and VCNs typically have underlying networks. + // This is an unmodifiable list and it will be returned as is in the getter. + @Nullable + private List mUnderlyingNetworks; + /** * Uid of the app making the request. */ @@ -184,6 +192,7 @@ public final class NetworkCapabilities implements Parcelable { mRequestorUid = Process.INVALID_UID; mRequestorPackageName = null; mSubIds = new ArraySet<>(); + mUnderlyingNetworks = null; } /** @@ -213,6 +222,9 @@ public final class NetworkCapabilities implements Parcelable { mRequestorUid = nc.mRequestorUid; mRequestorPackageName = nc.mRequestorPackageName; mSubIds = new ArraySet<>(nc.mSubIds); + // mUnderlyingNetworks is an unmodifiable list if non-null, so a defensive copy is not + // necessary. + mUnderlyingNetworks = nc.mUnderlyingNetworks; } /** @@ -698,6 +710,41 @@ public final class NetworkCapabilities implements Parcelable { setCapabilities(capabilities, new int[] {}); } + /** + * Set the underlying networks of this network. + * + * @param networks The underlying networks of this network. + * + * @hide + */ + public void setUnderlyingNetworks(@Nullable List networks) { + mUnderlyingNetworks = + (networks == null) ? null : Collections.unmodifiableList(new ArrayList<>(networks)); + } + + /** + * Get the underlying networks of this network. If the caller is not system privileged, this is + * always redacted to null and it will be never useful to the caller. + * + * @return
  • If the list is null, this network hasn't declared underlying networks.
  • + *
  • If the list is empty, this network has declared that it has no underlying + * networks or it doesn't run on any of the available networks.
  • + *
  • The list can contain multiple underlying networks, e.g. a VPN running over + * multiple networks at the same time.
  • + * + * @hide + */ + @SuppressLint("NullableCollection") + @Nullable + @SystemApi + public List getUnderlyingNetworks() { + return mUnderlyingNetworks; + } + + private boolean equalsUnderlyingNetworks(@NonNull NetworkCapabilities nc) { + return Objects.equals(getUnderlyingNetworks(), nc.getUnderlyingNetworks()); + } + /** * Tests for the presence of a capability on this instance. * @@ -1901,7 +1948,8 @@ public final class NetworkCapabilities implements Parcelable { && equalsPrivateDnsBroken(that) && equalsRequestor(that) && equalsAdministratorUids(that) - && equalsSubscriptionIds(that); + && equalsSubscriptionIds(that) + && equalsUnderlyingNetworks(that); } @Override @@ -1924,7 +1972,8 @@ public final class NetworkCapabilities implements Parcelable { + Objects.hashCode(mRequestorUid) * 53 + Objects.hashCode(mRequestorPackageName) * 59 + Arrays.hashCode(mAdministratorUids) * 61 - + Objects.hashCode(mSubIds) * 67; + + Objects.hashCode(mSubIds) * 67 + + Objects.hashCode(mUnderlyingNetworks) * 71; } @Override @@ -1959,6 +2008,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeInt(mRequestorUid); dest.writeString(mRequestorPackageName); dest.writeIntArray(CollectionUtils.toIntArray(mSubIds)); + dest.writeTypedList(mUnderlyingNetworks); } public static final @android.annotation.NonNull Creator CREATOR = @@ -1987,6 +2037,7 @@ public final class NetworkCapabilities implements Parcelable { for (int i = 0; i < subIdInts.length; i++) { netCap.mSubIds.add(subIdInts[i]); } + netCap.setUnderlyingNetworks(in.createTypedArrayList(Network.CREATOR)); return netCap; } @Override @@ -2078,6 +2129,16 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" SubscriptionIds: ").append(mSubIds); } + if (mUnderlyingNetworks != null && mUnderlyingNetworks.size() > 0) { + sb.append(" Underlying networks: ["); + final StringJoiner joiner = new StringJoiner(","); + for (int i = 0; i < mUnderlyingNetworks.size(); i++) { + joiner.add(mUnderlyingNetworks.get(i).toString()); + } + sb.append(joiner.toString()); + sb.append("]"); + } + sb.append("]"); return sb.toString(); } @@ -2795,6 +2856,17 @@ public final class NetworkCapabilities implements Parcelable { return this; } + /** + * Set the underlying networks of this network. + * + * @param networks The underlying networks of this network. + */ + @NonNull + public Builder setUnderlyingNetworks(@Nullable List networks) { + mCaps.setUnderlyingNetworks(networks); + return this; + } + /** * Builds the instance of the capabilities. * diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java index 502b4f6b58..a619d6b2d2 100644 --- a/tests/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java @@ -50,6 +50,7 @@ import static android.os.Process.INVALID_UID; import static com.android.modules.utils.build.SdkLevel.isAtLeastR; import static com.android.modules.utils.build.SdkLevel.isAtLeastS; +import static com.android.modules.utils.build.SdkLevel.isAtLeastT; import static com.android.testutils.MiscAsserts.assertEmpty; import static com.android.testutils.MiscAsserts.assertThrows; import static com.android.testutils.ParcelUtils.assertParcelSane; @@ -84,7 +85,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Set; @RunWith(AndroidJUnit4.class) @@ -342,7 +345,9 @@ public class NetworkCapabilitiesTest { } private void testParcelSane(NetworkCapabilities cap) { - if (isAtLeastS()) { + if (isAtLeastT()) { + assertParcelSane(cap, 17); + } else if (isAtLeastS()) { assertParcelSane(cap, 16); } else if (isAtLeastR()) { assertParcelSane(cap, 15); @@ -711,6 +716,47 @@ public class NetworkCapabilitiesTest { } } + @Test + public void testUnderlyingNetworks() { + assumeTrue(isAtLeastT()); + final NetworkCapabilities nc = new NetworkCapabilities(); + final Network network1 = new Network(100); + final Network network2 = new Network(101); + final ArrayList inputNetworks = new ArrayList<>(); + inputNetworks.add(network1); + inputNetworks.add(network2); + nc.setUnderlyingNetworks(inputNetworks); + final ArrayList outputNetworks = new ArrayList<>(nc.getUnderlyingNetworks()); + assertEquals(network1, outputNetworks.get(0)); + assertEquals(network2, outputNetworks.get(1)); + nc.setUnderlyingNetworks(null); + assertNull(nc.getUnderlyingNetworks()); + } + + @Test + public void testEqualsForUnderlyingNetworks() { + assumeTrue(isAtLeastT()); + final NetworkCapabilities nc1 = new NetworkCapabilities(); + final NetworkCapabilities nc2 = new NetworkCapabilities(); + assertEquals(nc1, nc2); + final Network network = new Network(100); + final ArrayList inputNetworks = new ArrayList<>(); + final ArrayList emptyList = new ArrayList<>(); + inputNetworks.add(network); + nc1.setUnderlyingNetworks(inputNetworks); + assertNotEquals(nc1, nc2); + nc2.setUnderlyingNetworks(inputNetworks); + assertEquals(nc1, nc2); + nc1.setUnderlyingNetworks(emptyList); + assertNotEquals(nc1, nc2); + nc2.setUnderlyingNetworks(emptyList); + assertEquals(nc1, nc2); + nc1.setUnderlyingNetworks(null); + assertNotEquals(nc1, nc2); + nc2.setUnderlyingNetworks(null); + assertEquals(nc1, nc2); + } + @Test public void testSetNetworkSpecifierOnMultiTransportNc() { // Sequence 1: Transport + Transport + NetworkSpecifier @@ -1109,7 +1155,7 @@ public class NetworkCapabilitiesTest { final TransportInfo transportInfo = new TransportInfo() {}; final String ssid = "TEST_SSID"; final String packageName = "com.google.test.networkcapabilities"; - final NetworkCapabilities nc = new NetworkCapabilities.Builder() + final NetworkCapabilities.Builder capBuilder = new NetworkCapabilities.Builder() .addTransportType(TRANSPORT_WIFI) .addTransportType(TRANSPORT_CELLULAR) .removeTransportType(TRANSPORT_CELLULAR) @@ -1125,8 +1171,14 @@ public class NetworkCapabilitiesTest { .setSignalStrength(signalStrength) .setSsid(ssid) .setRequestorUid(requestUid) - .setRequestorPackageName(packageName) - .build(); + .setRequestorPackageName(packageName); + final Network network1 = new Network(100); + final Network network2 = new Network(101); + final List inputNetworks = List.of(network1, network2); + if (isAtLeastT()) { + capBuilder.setUnderlyingNetworks(inputNetworks); + } + final NetworkCapabilities nc = capBuilder.build(); assertEquals(1, nc.getTransportTypes().length); assertEquals(TRANSPORT_WIFI, nc.getTransportTypes()[0]); assertTrue(nc.hasCapability(NET_CAPABILITY_EIMS)); @@ -1144,6 +1196,11 @@ public class NetworkCapabilitiesTest { assertEquals(ssid, nc.getSsid()); assertEquals(requestUid, nc.getRequestorUid()); assertEquals(packageName, nc.getRequestorPackageName()); + if (isAtLeastT()) { + final List outputNetworks = nc.getUnderlyingNetworks(); + assertEquals(network1, outputNetworks.get(0)); + assertEquals(network2, outputNetworks.get(1)); + } // Cannot assign null into NetworkCapabilities.Builder try { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(null);