From 0376684dea28c08cf1e7a99515dcac1bc5d24fc0 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Tue, 23 Nov 2021 18:36:10 -0800 Subject: [PATCH] Create VcnCellUnderlyingNetworkPriority Create VcnCellUnderlyingNetworkPriority to allow VCN callers to configure network prioritization. Bug: 206044122 Test: atest FrameworksVcnTests(new tests) Test: atest CtsVcnTestCases Change-Id: Ia7f44c5f956ff75c39e20d9761f1bd5c987644ee --- .../vcn/VcnCellUnderlyingNetworkPriority.java | 291 ++++++++++++++++++ .../net/vcn/VcnUnderlyingNetworkPriority.java | 2 +- .../vcn/util/PersistableBundleUtils.java | 16 + .../VcnCellUnderlyingNetworkPriorityTest.java | 76 +++++ 4 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java create mode 100644 tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java new file mode 100644 index 0000000000000..6b33e4f45c422 --- /dev/null +++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2021 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 android.net.vcn; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; +import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER; +import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER; +import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; +import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.PersistableBundle; +import android.telephony.SubscriptionInfo; +import android.telephony.TelephonyManager; +import android.util.ArraySet; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +// TODO: Add documents +/** @hide */ +public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetworkPriority { + private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds"; + @NonNull private final Set mAllowedNetworkPlmnIds; + private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds"; + @NonNull private final Set mAllowedSpecificCarrierIds; + + private static final String ALLOW_ROAMING_KEY = "mAllowRoaming"; + private final boolean mAllowRoaming; + + private static final String REQUIRE_OPPORTUNISTIC_KEY = "mRequireOpportunistic"; + private final boolean mRequireOpportunistic; + + private VcnCellUnderlyingNetworkPriority( + int networkQuality, + boolean allowMetered, + Set allowedNetworkPlmnIds, + Set allowedSpecificCarrierIds, + boolean allowRoaming, + boolean requireOpportunistic) { + super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, allowMetered); + mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds); + mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds); + mAllowRoaming = allowRoaming; + mRequireOpportunistic = requireOpportunistic; + + validate(); + } + + /** @hide */ + @Override + protected void validate() { + super.validate(); + validatePlmnIds(mAllowedNetworkPlmnIds); + Objects.requireNonNull(mAllowedSpecificCarrierIds, "allowedCarrierIds is null"); + } + + private static void validatePlmnIds(Set allowedNetworkPlmnIds) { + Objects.requireNonNull(allowedNetworkPlmnIds, "allowedNetworkPlmnIds is null"); + + // A valid PLMN is a concatenation of MNC and MCC, and thus consists of 5 or 6 decimal + // digits. + for (String id : allowedNetworkPlmnIds) { + if ((id.length() == 5 || id.length() == 6) && id.matches("[0-9]+")) { + continue; + } else { + throw new IllegalArgumentException("Found invalid PLMN ID: " + id); + } + } + } + + /** @hide */ + @NonNull + @VisibleForTesting(visibility = Visibility.PROTECTED) + public static VcnCellUnderlyingNetworkPriority fromPersistableBundle( + @NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle is null"); + + final int networkQuality = in.getInt(NETWORK_QUALITY_KEY); + final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY); + + final PersistableBundle plmnIdsBundle = + in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY); + Objects.requireNonNull(plmnIdsBundle, "plmnIdsBundle is null"); + final Set allowedNetworkPlmnIds = + new ArraySet( + PersistableBundleUtils.toList(plmnIdsBundle, STRING_DESERIALIZER)); + + final PersistableBundle specificCarrierIdsBundle = + in.getPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY); + Objects.requireNonNull(specificCarrierIdsBundle, "specificCarrierIdsBundle is null"); + final Set allowedSpecificCarrierIds = + new ArraySet( + PersistableBundleUtils.toList( + specificCarrierIdsBundle, INTEGER_DESERIALIZER)); + + final boolean allowRoaming = in.getBoolean(ALLOW_ROAMING_KEY); + final boolean requireOpportunistic = in.getBoolean(REQUIRE_OPPORTUNISTIC_KEY); + + return new VcnCellUnderlyingNetworkPriority( + networkQuality, + allowMetered, + allowedNetworkPlmnIds, + allowedSpecificCarrierIds, + allowRoaming, + requireOpportunistic); + } + + /** @hide */ + @Override + @NonNull + @VisibleForTesting(visibility = Visibility.PROTECTED) + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = super.toPersistableBundle(); + + final PersistableBundle plmnIdsBundle = + PersistableBundleUtils.fromList( + new ArrayList<>(mAllowedNetworkPlmnIds), STRING_SERIALIZER); + result.putPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY, plmnIdsBundle); + + final PersistableBundle specificCarrierIdsBundle = + PersistableBundleUtils.fromList( + new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER); + result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle); + + result.putBoolean(ALLOW_ROAMING_KEY, mAllowRoaming); + result.putBoolean(REQUIRE_OPPORTUNISTIC_KEY, mRequireOpportunistic); + + return result; + } + + /** Retrieve the allowed PLMN IDs, or an empty set if any PLMN ID is acceptable. */ + @NonNull + public Set getAllowedPlmnIds() { + return Collections.unmodifiableSet(mAllowedNetworkPlmnIds); + } + + /** + * Retrieve the allowed specific carrier IDs, or an empty set if any specific carrier ID is + * acceptable. + */ + @NonNull + public Set getAllowedSpecificCarrierIds() { + return Collections.unmodifiableSet(mAllowedSpecificCarrierIds); + } + + /** Return if roaming is allowed. */ + public boolean allowRoaming() { + return mAllowRoaming; + } + + /** Return if requiring an opportunistic network. */ + public boolean requireOpportunistic() { + return mRequireOpportunistic; + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), + mAllowedNetworkPlmnIds, + mAllowedSpecificCarrierIds, + mAllowRoaming, + mRequireOpportunistic); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!super.equals(other)) { + return false; + } + + if (!(other instanceof VcnCellUnderlyingNetworkPriority)) { + return false; + } + + final VcnCellUnderlyingNetworkPriority rhs = (VcnCellUnderlyingNetworkPriority) other; + return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds) + && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds) + && mAllowRoaming == rhs.mAllowRoaming + && mRequireOpportunistic == rhs.mRequireOpportunistic; + } + + /** This class is used to incrementally build WifiNetworkPriority objects. */ + public static class Builder extends VcnUnderlyingNetworkPriority.Builder { + @NonNull private final Set mAllowedNetworkPlmnIds = new ArraySet<>(); + @NonNull private final Set mAllowedSpecificCarrierIds = new ArraySet<>(); + + private boolean mAllowRoaming = false; + private boolean mRequireOpportunistic = false; + + /** Construct a Builder object. */ + public Builder() {} + + /** + * Set allowed operator PLMN IDs. + * + *

This is used to distinguish cases where roaming agreements may dictate a different + * priority from a partner's networks. + * + * @param allowedNetworkPlmnIds the allowed operator PLMN IDs in String. Defaults to an + * empty set, allowing ANY PLMN ID. A valid PLMN is a concatenation of MNC and MCC, and + * thus consists of 5 or 6 decimal digits. See {@link SubscriptionInfo#getMccString()} + * and {@link SubscriptionInfo#getMncString()}. + */ + @NonNull + public Builder setAllowedPlmnIds(@NonNull Set allowedNetworkPlmnIds) { + validatePlmnIds(allowedNetworkPlmnIds); + + mAllowedNetworkPlmnIds.clear(); + mAllowedNetworkPlmnIds.addAll(allowedNetworkPlmnIds); + return this; + } + + /** + * Set allowed specific carrier IDs. + * + * @param allowedSpecificCarrierIds the allowed specific carrier IDs. Defaults to an empty + * set, allowing ANY carrier ID. See {@link TelephonyManager#getSimSpecificCarrierId()}. + */ + @NonNull + public Builder setAllowedSpecificCarrierIds( + @NonNull Set allowedSpecificCarrierIds) { + Objects.requireNonNull(allowedSpecificCarrierIds, "allowedCarrierIds is null"); + mAllowedSpecificCarrierIds.clear(); + mAllowedSpecificCarrierIds.addAll(allowedSpecificCarrierIds); + return this; + } + + /** + * Set if roaming is allowed. + * + * @param allowRoaming the flag to indicate if roaming is allowed. Defaults to {@code + * false}. + */ + @NonNull + public Builder setAllowRoaming(boolean allowRoaming) { + mAllowRoaming = allowRoaming; + return this; + } + + /** + * Set if requiring an opportunistic network. + * + * @param requireOpportunistic the flag to indicate if caller requires an opportunistic + * network. Defaults to {@code false}. + */ + @NonNull + public Builder setRequireOpportunistic(boolean requireOpportunistic) { + mRequireOpportunistic = requireOpportunistic; + return this; + } + + /** Build the VcnCellUnderlyingNetworkPriority. */ + @NonNull + public VcnCellUnderlyingNetworkPriority build() { + return new VcnCellUnderlyingNetworkPriority( + mNetworkQuality, + mAllowMetered, + mAllowedNetworkPlmnIds, + mAllowedSpecificCarrierIds, + mAllowRoaming, + mRequireOpportunistic); + } + + /** @hide */ + @Override + Builder self() { + return this; + } + } +} diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java index 27750c659fc9e..82f6ae72b43c9 100644 --- a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java @@ -89,7 +89,7 @@ public abstract class VcnUnderlyingNetworkPriority { case NETWORK_PRIORITY_TYPE_WIFI: return VcnWifiUnderlyingNetworkPriority.fromPersistableBundle(in); case NETWORK_PRIORITY_TYPE_CELL: - throw new UnsupportedOperationException("Not implemented"); + return VcnCellUnderlyingNetworkPriority.fromPersistableBundle(in); default: throw new IllegalArgumentException( "Invalid networkPriorityType:" + networkPriorityType); diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java index 5c1b5ffb22099..1c675c2285540 100644 --- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java +++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java @@ -46,6 +46,7 @@ public class PersistableBundleUtils { private static final String PARCEL_UUID_KEY = "PARCEL_UUID"; private static final String BYTE_ARRAY_KEY = "BYTE_ARRAY_KEY"; private static final String INTEGER_KEY = "INTEGER_KEY"; + private static final String STRING_KEY = "STRING_KEY"; /** * Functional interface to convert an object of the specified type to a PersistableBundle. @@ -91,6 +92,21 @@ public class PersistableBundleUtils { return bundle.getInt(INTEGER_KEY); }; + /** Serializer to convert s String to a PersistableBundle. */ + public static final Serializer STRING_SERIALIZER = + (i) -> { + final PersistableBundle result = new PersistableBundle(); + result.putString(STRING_KEY, i); + return result; + }; + + /** Deserializer to convert a PersistableBundle to a String. */ + public static final Deserializer STRING_DESERIALIZER = + (bundle) -> { + Objects.requireNonNull(bundle, "PersistableBundle is null"); + return bundle.getString(STRING_KEY); + }; + /** * Converts a ParcelUuid to a PersistableBundle. * diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java new file mode 100644 index 0000000000000..3e47eb1fa4e33 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 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 android.net.vcn; + +import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY; +import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +public class VcnCellUnderlyingNetworkPriorityTest { + private static final Set ALLOWED_PLMN_IDS = new HashSet<>(); + private static final Set ALLOWED_CARRIER_IDS = new HashSet<>(); + + private static VcnCellUnderlyingNetworkPriority getTestNetworkPriority() { + return new VcnCellUnderlyingNetworkPriority.Builder() + .setNetworkQuality(NETWORK_QUALITY_OK) + .setAllowMetered(true /* allowMetered */) + .setAllowedPlmnIds(ALLOWED_PLMN_IDS) + .setAllowedSpecificCarrierIds(ALLOWED_CARRIER_IDS) + .setAllowRoaming(true /* allowRoaming */) + .setRequireOpportunistic(true /* requireOpportunistic */) + .build(); + } + + @Test + public void testBuilderAndGetters() { + final VcnCellUnderlyingNetworkPriority networkPriority = getTestNetworkPriority(); + assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality()); + assertTrue(networkPriority.allowMetered()); + assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedPlmnIds()); + assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getAllowedSpecificCarrierIds()); + assertTrue(networkPriority.allowRoaming()); + assertTrue(networkPriority.requireOpportunistic()); + } + + @Test + public void testBuilderAndGettersForDefaultValues() { + final VcnCellUnderlyingNetworkPriority networkPriority = + new VcnCellUnderlyingNetworkPriority.Builder().build(); + assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality()); + assertFalse(networkPriority.allowMetered()); + assertEquals(new HashSet(), networkPriority.getAllowedPlmnIds()); + assertEquals(new HashSet(), networkPriority.getAllowedSpecificCarrierIds()); + assertFalse(networkPriority.allowRoaming()); + assertFalse(networkPriority.requireOpportunistic()); + } + + @Test + public void testPersistableBundle() { + final VcnCellUnderlyingNetworkPriority networkPriority = getTestNetworkPriority(); + assertEquals( + networkPriority, + VcnUnderlyingNetworkPriority.fromPersistableBundle( + networkPriority.toPersistableBundle())); + } +}