From 52629838cb5d1aef54236eb517ed929bcbd1ccdb Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Wed, 13 Apr 2022 01:54:32 +0000 Subject: [PATCH] Add CarrierConfig to TelephonySubscriptionSnapshot This change ensures that carrier configs are saved as part of the snapshot, allowing all entities in the VCN to query telephony subscription information from the same source. Test: atest FrameworksVcnTests Change-Id: I31447d84fe98d6bd2cc78ead41746198728c400b --- core/java/android/net/vcn/VcnManager.java | 8 + .../vcn/TelephonySubscriptionTracker.java | 86 ++++++++- .../NetworkPriorityClassifier.java | 14 +- .../UnderlyingNetworkController.java | 30 +-- .../UnderlyingNetworkRecord.java | 9 +- .../vcn/util/PersistableBundleUtils.java | 180 ++++++++++++++++++ .../vcn/TelephonySubscriptionTrackerTest.java | 57 +++++- .../vcn/VcnGatewayConnectionTestBase.java | 1 + .../NetworkPriorityClassifierTest.java | 5 +- .../vcn/util/PersistableBundleUtilsTest.java | 82 ++++++++ 10 files changed, 425 insertions(+), 47 deletions(-) diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index f1b110ab29c8c..40e4083c02db2 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -104,6 +104,14 @@ public class VcnManager { // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz + /** List of Carrier Config options to extract from Carrier Config bundles. @hide */ + @NonNull + public static final String[] VCN_RELATED_CARRIER_CONFIG_KEYS = + new String[] { + VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, + VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY + }; + private static final Map< VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder> REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>(); diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index 89470ec00a6c7..5c305c6902af3 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -29,6 +29,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.vcn.VcnManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelUuid; @@ -47,6 +48,8 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.vcn.util.PersistableBundleUtils; +import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import java.util.ArrayList; import java.util.Collections; @@ -95,6 +98,10 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { // TODO (Android T+): Add ability to handle multiple subIds per slot. @NonNull private final Map mReadySubIdsBySlotId = new HashMap<>(); + + @NonNull + private final Map mSubIdToCarrierConfigMap = new HashMap<>(); + @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener; @NonNull @@ -250,7 +257,10 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { final TelephonySubscriptionSnapshot newSnapshot = new TelephonySubscriptionSnapshot( - mDeps.getActiveDataSubscriptionId(), newSubIdToInfoMap, privilegedPackages); + mDeps.getActiveDataSubscriptionId(), + newSubIdToInfoMap, + mSubIdToCarrierConfigMap, + privilegedPackages); // If snapshot was meaningfully updated, fire the callback if (!newSnapshot.equals(mCurrentSnapshot)) { @@ -311,47 +321,77 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { } if (SubscriptionManager.isValidSubscriptionId(subId)) { - final PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId); - if (mDeps.isConfigForIdentifiedCarrier(carrierConfigs)) { + final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId); + if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) { mReadySubIdsBySlotId.put(slotId, subId); + + final PersistableBundle minimized = + PersistableBundleUtils.minimizeBundle( + carrierConfig, VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); + if (minimized != null) { + mSubIdToCarrierConfigMap.put(subId, new PersistableBundleWrapper(minimized)); + } handleSubscriptionsChanged(); } } else { - mReadySubIdsBySlotId.remove(slotId); + final Integer oldSubid = mReadySubIdsBySlotId.remove(slotId); + if (oldSubid != null) { + mSubIdToCarrierConfigMap.remove(oldSubid); + } handleSubscriptionsChanged(); } } @VisibleForTesting(visibility = Visibility.PRIVATE) void setReadySubIdsBySlotId(Map readySubIdsBySlotId) { + mReadySubIdsBySlotId.clear(); mReadySubIdsBySlotId.putAll(readySubIdsBySlotId); } + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setSubIdToCarrierConfigMap( + Map subIdToCarrierConfigMap) { + mSubIdToCarrierConfigMap.clear(); + mSubIdToCarrierConfigMap.putAll(subIdToCarrierConfigMap); + } + @VisibleForTesting(visibility = Visibility.PRIVATE) Map getReadySubIdsBySlotId() { return Collections.unmodifiableMap(mReadySubIdsBySlotId); } + @VisibleForTesting(visibility = Visibility.PRIVATE) + Map getSubIdToCarrierConfigMap() { + return Collections.unmodifiableMap(mSubIdToCarrierConfigMap); + } + /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */ public static class TelephonySubscriptionSnapshot { private final int mActiveDataSubId; private final Map mSubIdToInfoMap; + private final Map mSubIdToCarrierConfigMap; private final Map> mPrivilegedPackages; public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT = new TelephonySubscriptionSnapshot( - INVALID_SUBSCRIPTION_ID, Collections.emptyMap(), Collections.emptyMap()); + INVALID_SUBSCRIPTION_ID, + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyMap()); @VisibleForTesting(visibility = Visibility.PRIVATE) TelephonySubscriptionSnapshot( int activeDataSubId, @NonNull Map subIdToInfoMap, + @NonNull Map subIdToCarrierConfigMap, @NonNull Map> privilegedPackages) { mActiveDataSubId = activeDataSubId; Objects.requireNonNull(subIdToInfoMap, "subIdToInfoMap was null"); Objects.requireNonNull(privilegedPackages, "privilegedPackages was null"); + Objects.requireNonNull(subIdToCarrierConfigMap, "subIdToCarrierConfigMap was null"); mSubIdToInfoMap = Collections.unmodifiableMap(subIdToInfoMap); + mSubIdToCarrierConfigMap = Collections.unmodifiableMap(subIdToCarrierConfigMap); final Map> unmodifiableInnerSets = new ArrayMap<>(); for (Entry> entry : privilegedPackages.entrySet()) { @@ -423,9 +463,40 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { : false; } + /** + * Retrieves a carrier config for a subscription in the provided group. + * + *

This method will prioritize non-opportunistic subscriptions, but will use the a + * carrier config for an opportunistic subscription if no other subscriptions are found. + */ + @Nullable + public PersistableBundleWrapper getCarrierConfigForSubGrp(@NonNull ParcelUuid subGrp) { + PersistableBundleWrapper result = null; + + for (int subId : getAllSubIdsInGroup(subGrp)) { + final PersistableBundleWrapper config = mSubIdToCarrierConfigMap.get(subId); + if (config != null) { + result = config; + + // Attempt to use (any) non-opportunistic subscription. If this subscription is + // opportunistic, continue and try to find a non-opportunistic subscription, + // using the opportunistic ones as a last resort. + if (!isOpportunistic(subId)) { + return config; + } + } + } + + return result; + } + @Override public int hashCode() { - return Objects.hash(mActiveDataSubId, mSubIdToInfoMap, mPrivilegedPackages); + return Objects.hash( + mActiveDataSubId, + mSubIdToInfoMap, + mSubIdToCarrierConfigMap, + mPrivilegedPackages); } @Override @@ -438,6 +509,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { return mActiveDataSubId == other.mActiveDataSubId && mSubIdToInfoMap.equals(other.mSubIdToInfoMap) + && mSubIdToCarrierConfigMap.equals(other.mSubIdToCarrierConfigMap) && mPrivilegedPackages.equals(other.mPrivilegedPackages); } @@ -448,6 +520,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { pw.println("mActiveDataSubId: " + mActiveDataSubId); pw.println("mSubIdToInfoMap: " + mSubIdToInfoMap); + pw.println("mSubIdToCarrierConfigMap: " + mSubIdToCarrierConfigMap); pw.println("mPrivilegedPackages: " + mPrivilegedPackages); pw.decreaseIndent(); @@ -458,6 +531,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { return "TelephonySubscriptionSnapshot{ " + "mActiveDataSubId=" + mActiveDataSubId + ", mSubIdToInfoMap=" + mSubIdToInfoMap + + ", mSubIdToCarrierConfigMap=" + mSubIdToCarrierConfigMap + ", mPrivilegedPackages=" + mPrivilegedPackages + " }"; } diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java index c96c1ee01a6d2..2f84fddc72789 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java @@ -24,6 +24,7 @@ import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static com.android.server.VcnManagementService.LOCAL_LOG; +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,7 +35,6 @@ import android.net.vcn.VcnManager; import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.net.vcn.VcnWifiUnderlyingNetworkTemplate; import android.os.ParcelUuid; -import android.os.PersistableBundle; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Slog; @@ -81,7 +81,7 @@ class NetworkPriorityClassifier { ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { + PersistableBundleWrapper carrierConfig) { // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED if (networkRecord.isBlocked) { @@ -119,7 +119,7 @@ class NetworkPriorityClassifier { ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { + PersistableBundleWrapper carrierConfig) { final NetworkCapabilities caps = networkRecord.networkCapabilities; final boolean isSelectedUnderlyingNetwork = currentlySelected != null @@ -181,7 +181,7 @@ class NetworkPriorityClassifier { VcnWifiUnderlyingNetworkTemplate networkPriority, UnderlyingNetworkRecord networkRecord, UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { + PersistableBundleWrapper carrierConfig) { final NetworkCapabilities caps = networkRecord.networkCapabilities; if (!caps.hasTransport(TRANSPORT_WIFI)) { @@ -204,7 +204,7 @@ class NetworkPriorityClassifier { private static boolean isWifiRssiAcceptable( UnderlyingNetworkRecord networkRecord, UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { + PersistableBundleWrapper carrierConfig) { final NetworkCapabilities caps = networkRecord.networkCapabilities; final boolean isSelectedNetwork = currentlySelected != null @@ -314,7 +314,7 @@ class NetworkPriorityClassifier { return false; } - static int getWifiEntryRssiThreshold(@Nullable PersistableBundle carrierConfig) { + static int getWifiEntryRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) { if (carrierConfig != null) { return carrierConfig.getInt( VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, @@ -323,7 +323,7 @@ class NetworkPriorityClassifier { return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; } - static int getWifiExitRssiThreshold(@Nullable PersistableBundle carrierConfig) { + static int getWifiExitRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) { if (carrierConfig != null) { return carrierConfig.getInt( VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java index a3babf7c9fff0..d474c5d33a93f 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java @@ -21,7 +21,7 @@ import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListen import static com.android.server.VcnManagementService.LOCAL_LOG; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiEntryRssiThreshold; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiExitRssiThreshold; -import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.isOpportunistic; +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -37,8 +37,6 @@ import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelUuid; -import android.os.PersistableBundle; -import android.telephony.CarrierConfigManager; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.ArrayMap; @@ -51,7 +49,6 @@ import com.android.server.vcn.VcnContext; import com.android.server.vcn.util.LogUtils; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -87,7 +84,7 @@ public class UnderlyingNetworkController { @Nullable private UnderlyingNetworkListener mRouteSelectionCallback; @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; - @Nullable private PersistableBundle mCarrierConfig; + @Nullable private PersistableBundleWrapper mCarrierConfig; private boolean mIsQuitting = false; @Nullable private UnderlyingNetworkRecord mCurrentRecord; @@ -124,25 +121,7 @@ public class UnderlyingNetworkController { .getSystemService(TelephonyManager.class) .registerTelephonyCallback(new HandlerExecutor(mHandler), mActiveDataSubIdListener); - // TODO: Listen for changes in carrier config that affect this. - for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { - PersistableBundle config = - mVcnContext - .getContext() - .getSystemService(CarrierConfigManager.class) - .getConfigForSubId(subId); - - if (config != null) { - mCarrierConfig = config; - - // Attempt to use (any) non-opportunistic subscription. If this subscription is - // opportunistic, continue and try to find a non-opportunistic subscription, using - // the opportunistic ones as a last resort. - if (!isOpportunistic(mLastSnapshot, Collections.singleton(subId))) { - break; - } - } - } + mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup); registerOrUpdateNetworkRequests(); } @@ -334,6 +313,9 @@ public class UnderlyingNetworkController { final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot; mLastSnapshot = newSnapshot; + // Update carrier config + mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup); + // Only trigger re-registration if subIds in this group have changed if (oldSnapshot .getAllSubIdsInGroup(mSubscriptionGroup) diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java index 06f92805ad2b4..319680e0b01c3 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java @@ -16,6 +16,8 @@ package com.android.server.vcn.routeselection; +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; + import android.annotation.NonNull; import android.annotation.Nullable; import android.net.LinkProperties; @@ -23,7 +25,6 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.ParcelUuid; -import android.os.PersistableBundle; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; @@ -68,7 +69,7 @@ public class UnderlyingNetworkRecord { ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { + PersistableBundleWrapper carrierConfig) { // Never changes after the underlying network record is created. if (mPriorityClass == PRIORITY_CLASS_INVALID) { mPriorityClass = @@ -113,7 +114,7 @@ public class UnderlyingNetworkRecord { ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { + PersistableBundleWrapper carrierConfig) { return (left, right) -> { final int leftIndex = left.getOrCalculatePriorityClass( @@ -167,7 +168,7 @@ public class UnderlyingNetworkRecord { ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { + PersistableBundleWrapper carrierConfig) { pw.println("UnderlyingNetworkRecord:"); pw.increaseIndent(); 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 1c675c2285540..08e8eebb87406 100644 --- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java +++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java @@ -28,11 +28,13 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.TreeSet; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -354,4 +356,182 @@ public class PersistableBundleUtils { } } } + + /** + * Returns a copy of the persistable bundle with only the specified keys + * + *

This allows for holding minimized copies for memory-saving purposes. + */ + @NonNull + public static PersistableBundle minimizeBundle( + @NonNull PersistableBundle bundle, String... keys) { + final PersistableBundle minimized = new PersistableBundle(); + + if (bundle == null) { + return minimized; + } + + for (String key : keys) { + if (bundle.containsKey(key)) { + final Object value = bundle.get(key); + if (value == null) { + continue; + } + + if (value instanceof Boolean) { + minimized.putBoolean(key, (Boolean) value); + } else if (value instanceof boolean[]) { + minimized.putBooleanArray(key, (boolean[]) value); + } else if (value instanceof Double) { + minimized.putDouble(key, (Double) value); + } else if (value instanceof double[]) { + minimized.putDoubleArray(key, (double[]) value); + } else if (value instanceof Integer) { + minimized.putInt(key, (Integer) value); + } else if (value instanceof int[]) { + minimized.putIntArray(key, (int[]) value); + } else if (value instanceof Long) { + minimized.putLong(key, (Long) value); + } else if (value instanceof long[]) { + minimized.putLongArray(key, (long[]) value); + } else if (value instanceof String) { + minimized.putString(key, (String) value); + } else if (value instanceof String[]) { + minimized.putStringArray(key, (String[]) value); + } else if (value instanceof PersistableBundle) { + minimized.putPersistableBundle(key, (PersistableBundle) value); + } else { + continue; + } + } + } + + return minimized; + } + + /** Builds a stable hashcode */ + public static int getHashCode(@Nullable PersistableBundle bundle) { + if (bundle == null) { + return -1; + } + + int iterativeHashcode = 0; + TreeSet treeSet = new TreeSet<>(bundle.keySet()); + for (String key : treeSet) { + Object val = bundle.get(key); + if (val instanceof PersistableBundle) { + iterativeHashcode = + Objects.hash(iterativeHashcode, key, getHashCode((PersistableBundle) val)); + } else { + iterativeHashcode = Objects.hash(iterativeHashcode, key, val); + } + } + + return iterativeHashcode; + } + + /** Checks for persistable bundle equality */ + public static boolean isEqual( + @Nullable PersistableBundle left, @Nullable PersistableBundle right) { + // Check for pointer equality & null equality + if (Objects.equals(left, right)) { + return true; + } + + // If only one of the two is null, but not the other, not equal by definition. + if (Objects.isNull(left) != Objects.isNull(right)) { + return false; + } + + if (!left.keySet().equals(right.keySet())) { + return false; + } + + for (String key : left.keySet()) { + Object leftVal = left.get(key); + Object rightVal = right.get(key); + + // Check for equality + if (Objects.equals(leftVal, rightVal)) { + continue; + } else if (Objects.isNull(leftVal) != Objects.isNull(rightVal)) { + // If only one of the two is null, but not the other, not equal by definition. + return false; + } else if (!Objects.equals(leftVal.getClass(), rightVal.getClass())) { + // If classes are different, not equal by definition. + return false; + } + if (leftVal instanceof PersistableBundle) { + if (!isEqual((PersistableBundle) leftVal, (PersistableBundle) rightVal)) { + return false; + } + } else if (leftVal.getClass().isArray()) { + if (leftVal instanceof boolean[]) { + if (!Arrays.equals((boolean[]) leftVal, (boolean[]) rightVal)) { + return false; + } + } else if (leftVal instanceof double[]) { + if (!Arrays.equals((double[]) leftVal, (double[]) rightVal)) { + return false; + } + } else if (leftVal instanceof int[]) { + if (!Arrays.equals((int[]) leftVal, (int[]) rightVal)) { + return false; + } + } else if (leftVal instanceof long[]) { + if (!Arrays.equals((long[]) leftVal, (long[]) rightVal)) { + return false; + } + } else if (!Arrays.equals((Object[]) leftVal, (Object[]) rightVal)) { + return false; + } + } else { + if (!Objects.equals(leftVal, rightVal)) { + return false; + } + } + } + + return true; + } + + /** + * Wrapper class around PersistableBundles to allow equality comparisons + * + *

This class exposes the minimal getters to retrieve values. + */ + public static class PersistableBundleWrapper { + @NonNull private final PersistableBundle mBundle; + + public PersistableBundleWrapper(@NonNull PersistableBundle bundle) { + mBundle = Objects.requireNonNull(bundle, "Bundle was null"); + } + + /** + * Retrieves the integer associated with the provided key. + * + * @param key the string key to query + * @param defaultValue the value to return if key does not exist + * @return the int value, or the default + */ + public int getInt(String key, int defaultValue) { + return mBundle.getInt(key, defaultValue); + } + + @Override + public int hashCode() { + return getHashCode(mBundle); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof PersistableBundleWrapper)) { + return false; + } + + final PersistableBundleWrapper other = (PersistableBundleWrapper) obj; + + return isEqual(mBundle, other.mBundle); + } + } } diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 5f606e1dab0c3..09080be9ee41f 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -26,6 +26,7 @@ import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -50,9 +51,11 @@ import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.vcn.VcnManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelUuid; +import android.os.PersistableBundle; import android.os.test.TestLooper; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; @@ -104,6 +107,26 @@ public class TelephonySubscriptionTrackerTest { TEST_SUBID_TO_INFO_MAP = Collections.unmodifiableMap(subIdToGroupMap); } + private static final String TEST_CARRIER_CONFIG_KEY_1 = "TEST_CARRIER_CONFIG_KEY_1"; + private static final String TEST_CARRIER_CONFIG_KEY_2 = "TEST_CARRIER_CONFIG_KEY_2"; + private static final PersistableBundle TEST_CARRIER_CONFIG = new PersistableBundle(); + private static final PersistableBundleWrapper TEST_CARRIER_CONFIG_WRAPPER; + private static final Map TEST_SUBID_TO_CARRIER_CONFIG_MAP; + + static { + TEST_CARRIER_CONFIG.putString( + VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, + VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY); + TEST_CARRIER_CONFIG.putString( + VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, + VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY); + TEST_CARRIER_CONFIG_WRAPPER = new PersistableBundleWrapper(TEST_CARRIER_CONFIG); + + final Map subIdToCarrierConfigMap = new HashMap<>(); + subIdToCarrierConfigMap.put(TEST_SUBSCRIPTION_ID_1, TEST_CARRIER_CONFIG_WRAPPER); + TEST_SUBID_TO_CARRIER_CONFIG_MAP = Collections.unmodifiableMap(subIdToCarrierConfigMap); + } + @NonNull private final Context mContext; @NonNull private final TestLooper mTestLooper; @NonNull private final Handler mHandler; @@ -144,6 +167,9 @@ public class TelephonySubscriptionTrackerTest { doReturn(mCarrierConfigManager) .when(mContext) .getSystemService(Context.CARRIER_CONFIG_SERVICE); + doReturn(TEST_CARRIER_CONFIG) + .when(mCarrierConfigManager) + .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1)); // subId 1, 2 are in same subGrp, only subId 1 is active doReturn(TEST_PARCEL_UUID).when(TEST_SUBINFO_1).getGroupUuid(); @@ -227,14 +253,24 @@ public class TelephonySubscriptionTrackerTest { private TelephonySubscriptionSnapshot buildExpectedSnapshot( Map subIdToInfoMap, Map> privilegedPackages) { - return new TelephonySubscriptionSnapshot(0, subIdToInfoMap, privilegedPackages); + return buildExpectedSnapshot(0, subIdToInfoMap, privilegedPackages); } private TelephonySubscriptionSnapshot buildExpectedSnapshot( int activeSubId, Map subIdToInfoMap, Map> privilegedPackages) { - return new TelephonySubscriptionSnapshot(activeSubId, subIdToInfoMap, privilegedPackages); + return buildExpectedSnapshot( + activeSubId, subIdToInfoMap, TEST_SUBID_TO_CARRIER_CONFIG_MAP, privilegedPackages); + } + + private TelephonySubscriptionSnapshot buildExpectedSnapshot( + int activeSubId, + Map subIdToInfoMap, + Map subIdToCarrierConfigMap, + Map> privilegedPackages) { + return new TelephonySubscriptionSnapshot( + activeSubId, subIdToInfoMap, subIdToCarrierConfigMap, privilegedPackages); } private void verifyNoActiveSubscriptions() { @@ -245,6 +281,8 @@ public class TelephonySubscriptionTrackerTest { private void setupReadySubIds() { mTelephonySubscriptionTracker.setReadySubIdsBySlotId( Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1)); + mTelephonySubscriptionTracker.setSubIdToCarrierConfigMap( + Collections.singletonMap(TEST_SUBSCRIPTION_ID_1, TEST_CARRIER_CONFIG_WRAPPER)); } private void setPrivilegedPackagesForMock(@NonNull List privilegedPackages) { @@ -300,6 +338,7 @@ public class TelephonySubscriptionTrackerTest { readySubIdsBySlotId.put(TEST_SIM_SLOT_INDEX + 1, TEST_SUBSCRIPTION_ID_1); mTelephonySubscriptionTracker.setReadySubIdsBySlotId(readySubIdsBySlotId); + mTelephonySubscriptionTracker.setSubIdToCarrierConfigMap(TEST_SUBID_TO_CARRIER_CONFIG_MAP); doReturn(1).when(mTelephonyManager).getActiveModemCount(); List carrierPrivilegesCallbacks = @@ -464,8 +503,16 @@ public class TelephonySubscriptionTrackerTest { mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false)); mTestLooper.dispatchAll(); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap()))); + verify(mCallback) + .onNewSnapshot( + eq( + buildExpectedSnapshot( + 0, TEST_SUBID_TO_INFO_MAP, emptyMap(), emptyMap()))); assertNull(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); + assertNull( + mTelephonySubscriptionTracker + .getSubIdToCarrierConfigMap() + .get(TEST_SUBSCRIPTION_ID_1)); } @Test @@ -493,7 +540,7 @@ public class TelephonySubscriptionTrackerTest { public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception { final TelephonySubscriptionSnapshot snapshot = new TelephonySubscriptionSnapshot( - TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap()); + TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap(), emptyMap()); assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_1)); assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2)); @@ -503,7 +550,7 @@ public class TelephonySubscriptionTrackerTest { public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception { final TelephonySubscriptionSnapshot snapshot = new TelephonySubscriptionSnapshot( - TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap()); + TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap(), emptyMap()); assertEquals( new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)), diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 5628321b59757..4d2568443885d 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -129,6 +129,7 @@ public class VcnGatewayConnectionTestBase { new TelephonySubscriptionSnapshot( TEST_SUB_ID, Collections.singletonMap(TEST_SUB_ID, TEST_SUB_INFO), + Collections.EMPTY_MAP, Collections.EMPTY_MAP); @NonNull protected final Context mContext; diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java index 6c849b5af8881..b0d68952c39d1 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java @@ -30,6 +30,7 @@ import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.ch import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule; import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName; +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -309,7 +310,9 @@ public class NetworkPriorityClassifierTest { wifiNetworkPriority, mWifiNetworkRecord, selectedNetworkRecord, - carrierConfig)); + carrierConfig == null + ? null + : new PersistableBundleWrapper(carrierConfig))); } @Test diff --git a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java index a44a734a2dce6..294f5c1f4842a 100644 --- a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java +++ b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java @@ -18,6 +18,8 @@ package com.android.server.vcn.util; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import android.os.PersistableBundle; @@ -211,4 +213,84 @@ public class PersistableBundleUtilsTest { assertEquals(testInt, result); } + + private PersistableBundle getTestBundle() { + final PersistableBundle bundle = new PersistableBundle(); + + bundle.putBoolean(TEST_KEY + "Boolean", true); + bundle.putBooleanArray(TEST_KEY + "BooleanArray", new boolean[] {true, false}); + bundle.putDouble(TEST_KEY + "Double", 0.1); + bundle.putDoubleArray(TEST_KEY + "DoubleArray", new double[] {0.1, 0.2, 0.3}); + bundle.putInt(TEST_KEY + "Int", 1); + bundle.putIntArray(TEST_KEY + "IntArray", new int[] {1, 2}); + bundle.putLong(TEST_KEY + "Long", 5L); + bundle.putLongArray(TEST_KEY + "LongArray", new long[] {0L, -1L, -2L}); + bundle.putString(TEST_KEY + "String", "TEST"); + bundle.putStringArray(TEST_KEY + "StringArray", new String[] {"foo", "bar", "bas"}); + bundle.putPersistableBundle( + TEST_KEY + "PersistableBundle", + new TestClass(1, TEST_INT_ARRAY, TEST_STRING_PREFIX, new PersistableBundle()) + .toPersistableBundle()); + + return bundle; + } + + @Test + public void testMinimizeBundle() throws Exception { + final String[] minimizedKeys = + new String[] { + TEST_KEY + "Boolean", + TEST_KEY + "BooleanArray", + TEST_KEY + "Double", + TEST_KEY + "DoubleArray", + TEST_KEY + "Int", + TEST_KEY + "IntArray", + TEST_KEY + "Long", + TEST_KEY + "LongArray", + TEST_KEY + "String", + TEST_KEY + "StringArray", + TEST_KEY + "PersistableBundle" + }; + + final PersistableBundle testBundle = getTestBundle(); + testBundle.putBoolean(TEST_KEY + "Boolean2", true); + + final PersistableBundle minimized = + PersistableBundleUtils.minimizeBundle(testBundle, minimizedKeys); + + // Verify that the minimized bundle is NOT the same in size OR values due to the extra + // Boolean2 key + assertFalse(PersistableBundleUtils.isEqual(testBundle, minimized)); + + // Verify that removing the extra key from the source bundle results in equality. + testBundle.remove(TEST_KEY + "Boolean2"); + assertTrue(PersistableBundleUtils.isEqual(testBundle, minimized)); + } + + @Test + public void testEquality_identical() throws Exception { + final PersistableBundle left = getTestBundle(); + final PersistableBundle right = getTestBundle(); + + assertTrue(PersistableBundleUtils.isEqual(left, right)); + } + + @Test + public void testEquality_different() throws Exception { + final PersistableBundle left = getTestBundle(); + final PersistableBundle right = getTestBundle(); + + left.putBoolean(TEST_KEY + "Boolean2", true); + assertFalse(PersistableBundleUtils.isEqual(left, right)); + + left.remove(TEST_KEY + "Boolean2"); + assertTrue(PersistableBundleUtils.isEqual(left, right)); + } + + @Test + public void testEquality_null() throws Exception { + assertFalse(PersistableBundleUtils.isEqual(getTestBundle(), null)); + assertFalse(PersistableBundleUtils.isEqual(null, getTestBundle())); + assertTrue(PersistableBundleUtils.isEqual(null, null)); + } }