Merge "Add CarrierConfig to TelephonySubscriptionSnapshot" am: 59aeaf29c0

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2063410

Change-Id: I5e09fea770734d71241008395045dd9ddddc0ca6
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Benedict Wong
2022-04-21 17:51:46 +00:00
committed by Automerger Merge Worker
10 changed files with 425 additions and 47 deletions

View File

@@ -104,6 +104,14 @@ public class VcnManager {
// TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz // 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< private static final Map<
VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder> VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder>
REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>(); REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>();

View File

@@ -29,6 +29,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.vcn.VcnManager;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerExecutor; import android.os.HandlerExecutor;
import android.os.ParcelUuid; import android.os.ParcelUuid;
@@ -47,6 +48,8 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.IndentingPrintWriter; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -95,6 +98,10 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
// TODO (Android T+): Add ability to handle multiple subIds per slot. // TODO (Android T+): Add ability to handle multiple subIds per slot.
@NonNull private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>(); @NonNull private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>();
@NonNull
private final Map<Integer, PersistableBundleWrapper> mSubIdToCarrierConfigMap = new HashMap<>();
@NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener; @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener;
@NonNull @NonNull
@@ -250,7 +257,10 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
final TelephonySubscriptionSnapshot newSnapshot = final TelephonySubscriptionSnapshot newSnapshot =
new TelephonySubscriptionSnapshot( new TelephonySubscriptionSnapshot(
mDeps.getActiveDataSubscriptionId(), newSubIdToInfoMap, privilegedPackages); mDeps.getActiveDataSubscriptionId(),
newSubIdToInfoMap,
mSubIdToCarrierConfigMap,
privilegedPackages);
// If snapshot was meaningfully updated, fire the callback // If snapshot was meaningfully updated, fire the callback
if (!newSnapshot.equals(mCurrentSnapshot)) { if (!newSnapshot.equals(mCurrentSnapshot)) {
@@ -311,47 +321,77 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
} }
if (SubscriptionManager.isValidSubscriptionId(subId)) { if (SubscriptionManager.isValidSubscriptionId(subId)) {
final PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId); final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId);
if (mDeps.isConfigForIdentifiedCarrier(carrierConfigs)) { if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) {
mReadySubIdsBySlotId.put(slotId, subId); 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(); handleSubscriptionsChanged();
} }
} else { } else {
mReadySubIdsBySlotId.remove(slotId); final Integer oldSubid = mReadySubIdsBySlotId.remove(slotId);
if (oldSubid != null) {
mSubIdToCarrierConfigMap.remove(oldSubid);
}
handleSubscriptionsChanged(); handleSubscriptionsChanged();
} }
} }
@VisibleForTesting(visibility = Visibility.PRIVATE) @VisibleForTesting(visibility = Visibility.PRIVATE)
void setReadySubIdsBySlotId(Map<Integer, Integer> readySubIdsBySlotId) { void setReadySubIdsBySlotId(Map<Integer, Integer> readySubIdsBySlotId) {
mReadySubIdsBySlotId.clear();
mReadySubIdsBySlotId.putAll(readySubIdsBySlotId); mReadySubIdsBySlotId.putAll(readySubIdsBySlotId);
} }
@VisibleForTesting(visibility = Visibility.PRIVATE)
void setSubIdToCarrierConfigMap(
Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap) {
mSubIdToCarrierConfigMap.clear();
mSubIdToCarrierConfigMap.putAll(subIdToCarrierConfigMap);
}
@VisibleForTesting(visibility = Visibility.PRIVATE) @VisibleForTesting(visibility = Visibility.PRIVATE)
Map<Integer, Integer> getReadySubIdsBySlotId() { Map<Integer, Integer> getReadySubIdsBySlotId() {
return Collections.unmodifiableMap(mReadySubIdsBySlotId); return Collections.unmodifiableMap(mReadySubIdsBySlotId);
} }
@VisibleForTesting(visibility = Visibility.PRIVATE)
Map<Integer, PersistableBundleWrapper> getSubIdToCarrierConfigMap() {
return Collections.unmodifiableMap(mSubIdToCarrierConfigMap);
}
/** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */ /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */
public static class TelephonySubscriptionSnapshot { public static class TelephonySubscriptionSnapshot {
private final int mActiveDataSubId; private final int mActiveDataSubId;
private final Map<Integer, SubscriptionInfo> mSubIdToInfoMap; private final Map<Integer, SubscriptionInfo> mSubIdToInfoMap;
private final Map<Integer, PersistableBundleWrapper> mSubIdToCarrierConfigMap;
private final Map<ParcelUuid, Set<String>> mPrivilegedPackages; private final Map<ParcelUuid, Set<String>> mPrivilegedPackages;
public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT = public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
new TelephonySubscriptionSnapshot( new TelephonySubscriptionSnapshot(
INVALID_SUBSCRIPTION_ID, Collections.emptyMap(), Collections.emptyMap()); INVALID_SUBSCRIPTION_ID,
Collections.emptyMap(),
Collections.emptyMap(),
Collections.emptyMap());
@VisibleForTesting(visibility = Visibility.PRIVATE) @VisibleForTesting(visibility = Visibility.PRIVATE)
TelephonySubscriptionSnapshot( TelephonySubscriptionSnapshot(
int activeDataSubId, int activeDataSubId,
@NonNull Map<Integer, SubscriptionInfo> subIdToInfoMap, @NonNull Map<Integer, SubscriptionInfo> subIdToInfoMap,
@NonNull Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap,
@NonNull Map<ParcelUuid, Set<String>> privilegedPackages) { @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) {
mActiveDataSubId = activeDataSubId; mActiveDataSubId = activeDataSubId;
Objects.requireNonNull(subIdToInfoMap, "subIdToInfoMap was null"); Objects.requireNonNull(subIdToInfoMap, "subIdToInfoMap was null");
Objects.requireNonNull(privilegedPackages, "privilegedPackages was null"); Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");
Objects.requireNonNull(subIdToCarrierConfigMap, "subIdToCarrierConfigMap was null");
mSubIdToInfoMap = Collections.unmodifiableMap(subIdToInfoMap); mSubIdToInfoMap = Collections.unmodifiableMap(subIdToInfoMap);
mSubIdToCarrierConfigMap = Collections.unmodifiableMap(subIdToCarrierConfigMap);
final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>(); final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>();
for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) { for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) {
@@ -423,9 +463,40 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
: false; : false;
} }
/**
* Retrieves a carrier config for a subscription in the provided group.
*
* <p>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 @Override
public int hashCode() { public int hashCode() {
return Objects.hash(mActiveDataSubId, mSubIdToInfoMap, mPrivilegedPackages); return Objects.hash(
mActiveDataSubId,
mSubIdToInfoMap,
mSubIdToCarrierConfigMap,
mPrivilegedPackages);
} }
@Override @Override
@@ -438,6 +509,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
return mActiveDataSubId == other.mActiveDataSubId return mActiveDataSubId == other.mActiveDataSubId
&& mSubIdToInfoMap.equals(other.mSubIdToInfoMap) && mSubIdToInfoMap.equals(other.mSubIdToInfoMap)
&& mSubIdToCarrierConfigMap.equals(other.mSubIdToCarrierConfigMap)
&& mPrivilegedPackages.equals(other.mPrivilegedPackages); && mPrivilegedPackages.equals(other.mPrivilegedPackages);
} }
@@ -448,6 +520,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
pw.println("mActiveDataSubId: " + mActiveDataSubId); pw.println("mActiveDataSubId: " + mActiveDataSubId);
pw.println("mSubIdToInfoMap: " + mSubIdToInfoMap); pw.println("mSubIdToInfoMap: " + mSubIdToInfoMap);
pw.println("mSubIdToCarrierConfigMap: " + mSubIdToCarrierConfigMap);
pw.println("mPrivilegedPackages: " + mPrivilegedPackages); pw.println("mPrivilegedPackages: " + mPrivilegedPackages);
pw.decreaseIndent(); pw.decreaseIndent();
@@ -458,6 +531,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
return "TelephonySubscriptionSnapshot{ " return "TelephonySubscriptionSnapshot{ "
+ "mActiveDataSubId=" + mActiveDataSubId + "mActiveDataSubId=" + mActiveDataSubId
+ ", mSubIdToInfoMap=" + mSubIdToInfoMap + ", mSubIdToInfoMap=" + mSubIdToInfoMap
+ ", mSubIdToCarrierConfigMap=" + mSubIdToCarrierConfigMap
+ ", mPrivilegedPackages=" + mPrivilegedPackages + ", mPrivilegedPackages=" + mPrivilegedPackages
+ " }"; + " }";
} }

View File

@@ -24,6 +24,7 @@ import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
import static com.android.server.VcnManagementService.LOCAL_LOG; import static com.android.server.VcnManagementService.LOCAL_LOG;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
@@ -34,7 +35,6 @@ import android.net.vcn.VcnManager;
import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.net.vcn.VcnUnderlyingNetworkTemplate;
import android.net.vcn.VcnWifiUnderlyingNetworkTemplate; import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.os.PersistableBundle;
import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.util.Slog; import android.util.Slog;
@@ -81,7 +81,7 @@ class NetworkPriorityClassifier {
ParcelUuid subscriptionGroup, ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot, TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected, UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) { PersistableBundleWrapper carrierConfig) {
// mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
if (networkRecord.isBlocked) { if (networkRecord.isBlocked) {
@@ -119,7 +119,7 @@ class NetworkPriorityClassifier {
ParcelUuid subscriptionGroup, ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot, TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected, UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) { PersistableBundleWrapper carrierConfig) {
final NetworkCapabilities caps = networkRecord.networkCapabilities; final NetworkCapabilities caps = networkRecord.networkCapabilities;
final boolean isSelectedUnderlyingNetwork = final boolean isSelectedUnderlyingNetwork =
currentlySelected != null currentlySelected != null
@@ -181,7 +181,7 @@ class NetworkPriorityClassifier {
VcnWifiUnderlyingNetworkTemplate networkPriority, VcnWifiUnderlyingNetworkTemplate networkPriority,
UnderlyingNetworkRecord networkRecord, UnderlyingNetworkRecord networkRecord,
UnderlyingNetworkRecord currentlySelected, UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) { PersistableBundleWrapper carrierConfig) {
final NetworkCapabilities caps = networkRecord.networkCapabilities; final NetworkCapabilities caps = networkRecord.networkCapabilities;
if (!caps.hasTransport(TRANSPORT_WIFI)) { if (!caps.hasTransport(TRANSPORT_WIFI)) {
@@ -204,7 +204,7 @@ class NetworkPriorityClassifier {
private static boolean isWifiRssiAcceptable( private static boolean isWifiRssiAcceptable(
UnderlyingNetworkRecord networkRecord, UnderlyingNetworkRecord networkRecord,
UnderlyingNetworkRecord currentlySelected, UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) { PersistableBundleWrapper carrierConfig) {
final NetworkCapabilities caps = networkRecord.networkCapabilities; final NetworkCapabilities caps = networkRecord.networkCapabilities;
final boolean isSelectedNetwork = final boolean isSelectedNetwork =
currentlySelected != null currentlySelected != null
@@ -314,7 +314,7 @@ class NetworkPriorityClassifier {
return false; return false;
} }
static int getWifiEntryRssiThreshold(@Nullable PersistableBundle carrierConfig) { static int getWifiEntryRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) {
if (carrierConfig != null) { if (carrierConfig != null) {
return carrierConfig.getInt( return carrierConfig.getInt(
VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
@@ -323,7 +323,7 @@ class NetworkPriorityClassifier {
return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
} }
static int getWifiExitRssiThreshold(@Nullable PersistableBundle carrierConfig) { static int getWifiExitRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) {
if (carrierConfig != null) { if (carrierConfig != null) {
return carrierConfig.getInt( return carrierConfig.getInt(
VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,

View File

@@ -21,7 +21,7 @@ import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListen
import static com.android.server.VcnManagementService.LOCAL_LOG; 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.getWifiEntryRssiThreshold;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiExitRssiThreshold; 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.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
@@ -37,8 +37,6 @@ import android.net.vcn.VcnUnderlyingNetworkTemplate;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerExecutor; import android.os.HandlerExecutor;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.TelephonyCallback; import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.util.ArrayMap; import android.util.ArrayMap;
@@ -51,7 +49,6 @@ import com.android.server.vcn.VcnContext;
import com.android.server.vcn.util.LogUtils; import com.android.server.vcn.util.LogUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -87,7 +84,7 @@ public class UnderlyingNetworkController {
@Nullable private UnderlyingNetworkListener mRouteSelectionCallback; @Nullable private UnderlyingNetworkListener mRouteSelectionCallback;
@NonNull private TelephonySubscriptionSnapshot mLastSnapshot; @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
@Nullable private PersistableBundle mCarrierConfig; @Nullable private PersistableBundleWrapper mCarrierConfig;
private boolean mIsQuitting = false; private boolean mIsQuitting = false;
@Nullable private UnderlyingNetworkRecord mCurrentRecord; @Nullable private UnderlyingNetworkRecord mCurrentRecord;
@@ -124,25 +121,7 @@ public class UnderlyingNetworkController {
.getSystemService(TelephonyManager.class) .getSystemService(TelephonyManager.class)
.registerTelephonyCallback(new HandlerExecutor(mHandler), mActiveDataSubIdListener); .registerTelephonyCallback(new HandlerExecutor(mHandler), mActiveDataSubIdListener);
// TODO: Listen for changes in carrier config that affect this. mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
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;
}
}
}
registerOrUpdateNetworkRequests(); registerOrUpdateNetworkRequests();
} }
@@ -334,6 +313,9 @@ public class UnderlyingNetworkController {
final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot; final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot;
mLastSnapshot = newSnapshot; mLastSnapshot = newSnapshot;
// Update carrier config
mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
// Only trigger re-registration if subIds in this group have changed // Only trigger re-registration if subIds in this group have changed
if (oldSnapshot if (oldSnapshot
.getAllSubIdsInGroup(mSubscriptionGroup) .getAllSubIdsInGroup(mSubscriptionGroup)

View File

@@ -16,6 +16,8 @@
package com.android.server.vcn.routeselection; package com.android.server.vcn.routeselection;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.net.LinkProperties; import android.net.LinkProperties;
@@ -23,7 +25,6 @@ import android.net.Network;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.net.vcn.VcnUnderlyingNetworkTemplate;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.os.PersistableBundle;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -68,7 +69,7 @@ public class UnderlyingNetworkRecord {
ParcelUuid subscriptionGroup, ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot, TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected, UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) { PersistableBundleWrapper carrierConfig) {
// Never changes after the underlying network record is created. // Never changes after the underlying network record is created.
if (mPriorityClass == PRIORITY_CLASS_INVALID) { if (mPriorityClass == PRIORITY_CLASS_INVALID) {
mPriorityClass = mPriorityClass =
@@ -113,7 +114,7 @@ public class UnderlyingNetworkRecord {
ParcelUuid subscriptionGroup, ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot, TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected, UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) { PersistableBundleWrapper carrierConfig) {
return (left, right) -> { return (left, right) -> {
final int leftIndex = final int leftIndex =
left.getOrCalculatePriorityClass( left.getOrCalculatePriorityClass(
@@ -167,7 +168,7 @@ public class UnderlyingNetworkRecord {
ParcelUuid subscriptionGroup, ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot, TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected, UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) { PersistableBundleWrapper carrierConfig) {
pw.println("UnderlyingNetworkRecord:"); pw.println("UnderlyingNetworkRecord:");
pw.increaseIndent(); pw.increaseIndent();

View File

@@ -28,11 +28,13 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.TreeSet;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; 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
*
* <p>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<String> 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
*
* <p>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);
}
}
} }

View File

@@ -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.TelephonySubscriptionSnapshot;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; 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.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@@ -50,9 +51,11 @@ import android.annotation.NonNull;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.vcn.VcnManager;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerExecutor; import android.os.HandlerExecutor;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.os.PersistableBundle;
import android.os.test.TestLooper; import android.os.test.TestLooper;
import android.telephony.CarrierConfigManager; import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionInfo;
@@ -104,6 +107,26 @@ public class TelephonySubscriptionTrackerTest {
TEST_SUBID_TO_INFO_MAP = Collections.unmodifiableMap(subIdToGroupMap); 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<Integer, PersistableBundleWrapper> 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<Integer, PersistableBundleWrapper> 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 Context mContext;
@NonNull private final TestLooper mTestLooper; @NonNull private final TestLooper mTestLooper;
@NonNull private final Handler mHandler; @NonNull private final Handler mHandler;
@@ -144,6 +167,9 @@ public class TelephonySubscriptionTrackerTest {
doReturn(mCarrierConfigManager) doReturn(mCarrierConfigManager)
.when(mContext) .when(mContext)
.getSystemService(Context.CARRIER_CONFIG_SERVICE); .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 // subId 1, 2 are in same subGrp, only subId 1 is active
doReturn(TEST_PARCEL_UUID).when(TEST_SUBINFO_1).getGroupUuid(); doReturn(TEST_PARCEL_UUID).when(TEST_SUBINFO_1).getGroupUuid();
@@ -227,14 +253,24 @@ public class TelephonySubscriptionTrackerTest {
private TelephonySubscriptionSnapshot buildExpectedSnapshot( private TelephonySubscriptionSnapshot buildExpectedSnapshot(
Map<Integer, SubscriptionInfo> subIdToInfoMap, Map<Integer, SubscriptionInfo> subIdToInfoMap,
Map<ParcelUuid, Set<String>> privilegedPackages) { Map<ParcelUuid, Set<String>> privilegedPackages) {
return new TelephonySubscriptionSnapshot(0, subIdToInfoMap, privilegedPackages); return buildExpectedSnapshot(0, subIdToInfoMap, privilegedPackages);
} }
private TelephonySubscriptionSnapshot buildExpectedSnapshot( private TelephonySubscriptionSnapshot buildExpectedSnapshot(
int activeSubId, int activeSubId,
Map<Integer, SubscriptionInfo> subIdToInfoMap, Map<Integer, SubscriptionInfo> subIdToInfoMap,
Map<ParcelUuid, Set<String>> privilegedPackages) { Map<ParcelUuid, Set<String>> privilegedPackages) {
return new TelephonySubscriptionSnapshot(activeSubId, subIdToInfoMap, privilegedPackages); return buildExpectedSnapshot(
activeSubId, subIdToInfoMap, TEST_SUBID_TO_CARRIER_CONFIG_MAP, privilegedPackages);
}
private TelephonySubscriptionSnapshot buildExpectedSnapshot(
int activeSubId,
Map<Integer, SubscriptionInfo> subIdToInfoMap,
Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap,
Map<ParcelUuid, Set<String>> privilegedPackages) {
return new TelephonySubscriptionSnapshot(
activeSubId, subIdToInfoMap, subIdToCarrierConfigMap, privilegedPackages);
} }
private void verifyNoActiveSubscriptions() { private void verifyNoActiveSubscriptions() {
@@ -245,6 +281,8 @@ public class TelephonySubscriptionTrackerTest {
private void setupReadySubIds() { private void setupReadySubIds() {
mTelephonySubscriptionTracker.setReadySubIdsBySlotId( mTelephonySubscriptionTracker.setReadySubIdsBySlotId(
Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1)); 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<String> privilegedPackages) { private void setPrivilegedPackagesForMock(@NonNull List<String> privilegedPackages) {
@@ -300,6 +338,7 @@ public class TelephonySubscriptionTrackerTest {
readySubIdsBySlotId.put(TEST_SIM_SLOT_INDEX + 1, TEST_SUBSCRIPTION_ID_1); readySubIdsBySlotId.put(TEST_SIM_SLOT_INDEX + 1, TEST_SUBSCRIPTION_ID_1);
mTelephonySubscriptionTracker.setReadySubIdsBySlotId(readySubIdsBySlotId); mTelephonySubscriptionTracker.setReadySubIdsBySlotId(readySubIdsBySlotId);
mTelephonySubscriptionTracker.setSubIdToCarrierConfigMap(TEST_SUBID_TO_CARRIER_CONFIG_MAP);
doReturn(1).when(mTelephonyManager).getActiveModemCount(); doReturn(1).when(mTelephonyManager).getActiveModemCount();
List<CarrierPrivilegesCallback> carrierPrivilegesCallbacks = List<CarrierPrivilegesCallback> carrierPrivilegesCallbacks =
@@ -464,8 +503,16 @@ public class TelephonySubscriptionTrackerTest {
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false)); mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false));
mTestLooper.dispatchAll(); 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.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
assertNull(
mTelephonySubscriptionTracker
.getSubIdToCarrierConfigMap()
.get(TEST_SUBSCRIPTION_ID_1));
} }
@Test @Test
@@ -493,7 +540,7 @@ public class TelephonySubscriptionTrackerTest {
public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception { public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception {
final TelephonySubscriptionSnapshot snapshot = final TelephonySubscriptionSnapshot snapshot =
new TelephonySubscriptionSnapshot( 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_1));
assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2)); assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2));
@@ -503,7 +550,7 @@ public class TelephonySubscriptionTrackerTest {
public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception { public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception {
final TelephonySubscriptionSnapshot snapshot = final TelephonySubscriptionSnapshot snapshot =
new TelephonySubscriptionSnapshot( new TelephonySubscriptionSnapshot(
TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap()); TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap(), emptyMap());
assertEquals( assertEquals(
new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)), new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)),

View File

@@ -138,6 +138,7 @@ public class VcnGatewayConnectionTestBase {
new TelephonySubscriptionSnapshot( new TelephonySubscriptionSnapshot(
TEST_SUB_ID, TEST_SUB_ID,
Collections.singletonMap(TEST_SUB_ID, TEST_SUB_INFO), Collections.singletonMap(TEST_SUB_ID, TEST_SUB_INFO),
Collections.EMPTY_MAP,
Collections.EMPTY_MAP); Collections.EMPTY_MAP);
@NonNull protected final Context mContext; @NonNull protected final Context mContext;

View File

@@ -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.checkMatchesPriorityRule;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule;
import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@@ -309,7 +310,9 @@ public class NetworkPriorityClassifierTest {
wifiNetworkPriority, wifiNetworkPriority,
mWifiNetworkRecord, mWifiNetworkRecord,
selectedNetworkRecord, selectedNetworkRecord,
carrierConfig)); carrierConfig == null
? null
: new PersistableBundleWrapper(carrierConfig)));
} }
@Test @Test

View File

@@ -18,6 +18,8 @@ package com.android.server.vcn.util;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.os.PersistableBundle; import android.os.PersistableBundle;
@@ -211,4 +213,84 @@ public class PersistableBundleUtilsTest {
assertEquals(testInt, result); 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));
}
} }