Merge changes I3b769562,I63203188,Ia9396d66,Ifec34fad
* changes: Pull VcnNetworkProvider out into a separate class Verify carrier privileges for VCN-providing packages Add TelephonySubscriptionTracker to VcnMgmtSvc Add basic VcnMgmtSvc --> Vcn signals (startup, teardown, NetworkReq)
This commit is contained in:
@@ -23,6 +23,6 @@ import android.os.ParcelUuid;
|
||||
* @hide
|
||||
*/
|
||||
interface IVcnManagementService {
|
||||
void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config);
|
||||
void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName);
|
||||
void clearVcnConfig(in ParcelUuid subscriptionGroup);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.PersistableBundle;
|
||||
@@ -45,11 +46,17 @@ import java.util.Set;
|
||||
public final class VcnConfig implements Parcelable {
|
||||
@NonNull private static final String TAG = VcnConfig.class.getSimpleName();
|
||||
|
||||
private static final String PACKAGE_NAME_KEY = "mPackageName";
|
||||
@NonNull private final String mPackageName;
|
||||
|
||||
private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
|
||||
@NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
|
||||
|
||||
private VcnConfig(@NonNull Set<VcnGatewayConnectionConfig> tunnelConfigs) {
|
||||
mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs);
|
||||
private VcnConfig(
|
||||
@NonNull String packageName,
|
||||
@NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
|
||||
mPackageName = packageName;
|
||||
mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs);
|
||||
|
||||
validate();
|
||||
}
|
||||
@@ -61,6 +68,8 @@ public final class VcnConfig implements Parcelable {
|
||||
*/
|
||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||
public VcnConfig(@NonNull PersistableBundle in) {
|
||||
mPackageName = in.getString(PACKAGE_NAME_KEY);
|
||||
|
||||
final PersistableBundle gatewayConnectionConfigsBundle =
|
||||
in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY);
|
||||
mGatewayConnectionConfigs =
|
||||
@@ -72,8 +81,19 @@ public final class VcnConfig implements Parcelable {
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
Objects.requireNonNull(mPackageName, "packageName was null");
|
||||
Preconditions.checkCollectionNotEmpty(
|
||||
mGatewayConnectionConfigs, "gatewayConnectionConfigs");
|
||||
mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the package name of the provisioning app.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public String getProvisioningPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/** Retrieves the set of configured tunnels. */
|
||||
@@ -91,6 +111,8 @@ public final class VcnConfig implements Parcelable {
|
||||
public PersistableBundle toPersistableBundle() {
|
||||
final PersistableBundle result = new PersistableBundle();
|
||||
|
||||
result.putString(PACKAGE_NAME_KEY, mPackageName);
|
||||
|
||||
final PersistableBundle gatewayConnectionConfigsBundle =
|
||||
PersistableBundleUtils.fromList(
|
||||
new ArrayList<>(mGatewayConnectionConfigs),
|
||||
@@ -102,7 +124,7 @@ public final class VcnConfig implements Parcelable {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mGatewayConnectionConfigs);
|
||||
return Objects.hash(mPackageName, mGatewayConnectionConfigs);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,7 +134,8 @@ public final class VcnConfig implements Parcelable {
|
||||
}
|
||||
|
||||
final VcnConfig rhs = (VcnConfig) other;
|
||||
return mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
|
||||
return mPackageName.equals(rhs.mPackageName)
|
||||
&& mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
|
||||
}
|
||||
|
||||
// Parcelable methods
|
||||
@@ -143,9 +166,17 @@ public final class VcnConfig implements Parcelable {
|
||||
|
||||
/** This class is used to incrementally build {@link VcnConfig} objects. */
|
||||
public static class Builder {
|
||||
@NonNull private final String mPackageName;
|
||||
|
||||
@NonNull
|
||||
private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
|
||||
|
||||
public Builder(@NonNull Context context) {
|
||||
Objects.requireNonNull(context, "context was null");
|
||||
|
||||
mPackageName = context.getOpPackageName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a configuration for an individual gateway connection.
|
||||
*
|
||||
@@ -168,7 +199,7 @@ public final class VcnConfig implements Parcelable {
|
||||
*/
|
||||
@NonNull
|
||||
public VcnConfig build() {
|
||||
return new VcnConfig(mGatewayConnectionConfigs);
|
||||
return new VcnConfig(mPackageName, mGatewayConnectionConfigs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public final class VcnManager {
|
||||
requireNonNull(config, "config was null");
|
||||
|
||||
try {
|
||||
mService.setVcnConfig(subscriptionGroup, config);
|
||||
mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName());
|
||||
} catch (ServiceSpecificException e) {
|
||||
throw new IOException(e);
|
||||
} catch (RemoteException e) {
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
|
||||
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkProvider;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.vcn.IVcnManagementService;
|
||||
import android.net.vcn.VcnConfig;
|
||||
import android.os.Binder;
|
||||
@@ -43,6 +45,10 @@ import android.util.Slog;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.annotations.VisibleForTesting.Visibility;
|
||||
import com.android.server.vcn.TelephonySubscriptionTracker;
|
||||
import com.android.server.vcn.Vcn;
|
||||
import com.android.server.vcn.VcnContext;
|
||||
import com.android.server.vcn.VcnNetworkProvider;
|
||||
import com.android.server.vcn.util.PersistableBundleUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -51,6 +57,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
|
||||
@@ -115,6 +122,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||
static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml";
|
||||
|
||||
// TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS
|
||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||
static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
|
||||
|
||||
/* Binder context for this service */
|
||||
@NonNull private final Context mContext;
|
||||
@NonNull private final Dependencies mDeps;
|
||||
@@ -122,11 +133,23 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
@NonNull private final Looper mLooper;
|
||||
@NonNull private final Handler mHandler;
|
||||
@NonNull private final VcnNetworkProvider mNetworkProvider;
|
||||
@NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
|
||||
@NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
|
||||
@NonNull private final VcnContext mVcnContext;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@NonNull
|
||||
private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@NonNull
|
||||
private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@NonNull
|
||||
private TelephonySubscriptionSnapshot mLastSnapshot =
|
||||
TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT;
|
||||
|
||||
@NonNull private final Object mLock = new Object();
|
||||
|
||||
@NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper;
|
||||
@@ -139,8 +162,12 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
mLooper = mDeps.getLooper();
|
||||
mHandler = new Handler(mLooper);
|
||||
mNetworkProvider = new VcnNetworkProvider(mContext, mLooper);
|
||||
mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback();
|
||||
mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker(
|
||||
mContext, mLooper, mTelephonySubscriptionTrackerCb);
|
||||
|
||||
mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
|
||||
mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider);
|
||||
|
||||
// Run on handler to ensure I/O does not block system server startup
|
||||
mHandler.post(() -> {
|
||||
@@ -174,7 +201,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
mConfigs.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
// TODO: Trigger re-evaluation of active VCNs; start/stop VCNs as needed.
|
||||
|
||||
// Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty
|
||||
// snapshot, and therefore safe even before telephony subscriptions are loaded.
|
||||
mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -203,6 +233,14 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
return mHandlerThread.getLooper();
|
||||
}
|
||||
|
||||
/** Creates a new VcnInstance using the provided configuration */
|
||||
public TelephonySubscriptionTracker newTelephonySubscriptionTracker(
|
||||
@NonNull Context context,
|
||||
@NonNull Looper looper,
|
||||
@NonNull TelephonySubscriptionTrackerCallback callback) {
|
||||
return new TelephonySubscriptionTracker(context, new Handler(looper), callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the caller's UID
|
||||
*
|
||||
@@ -225,12 +263,29 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
newPersistableBundleLockingReadWriteHelper(@NonNull String path) {
|
||||
return new PersistableBundleUtils.LockingReadWriteHelper(path);
|
||||
}
|
||||
|
||||
/** Creates a new VcnContext */
|
||||
public VcnContext newVcnContext(
|
||||
@NonNull Context context,
|
||||
@NonNull Looper looper,
|
||||
@NonNull VcnNetworkProvider vcnNetworkProvider) {
|
||||
return new VcnContext(context, looper, vcnNetworkProvider);
|
||||
}
|
||||
|
||||
/** Creates a new Vcn instance using the provided configuration */
|
||||
public Vcn newVcn(
|
||||
@NonNull VcnContext vcnContext,
|
||||
@NonNull ParcelUuid subscriptionGroup,
|
||||
@NonNull VcnConfig config) {
|
||||
return new Vcn(vcnContext, subscriptionGroup, config);
|
||||
}
|
||||
}
|
||||
|
||||
/** Notifies the VcnManagementService that external dependencies can be set up. */
|
||||
public void systemReady() {
|
||||
mContext.getSystemService(ConnectivityManager.class)
|
||||
.registerNetworkProvider(mNetworkProvider);
|
||||
mTelephonySubscriptionTracker.register();
|
||||
}
|
||||
|
||||
private void enforcePrimaryUser() {
|
||||
@@ -277,27 +332,112 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
"Carrier privilege required for subscription group to set VCN Config");
|
||||
}
|
||||
|
||||
private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
|
||||
/**
|
||||
* Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
|
||||
*
|
||||
* <p>Start any unstarted VCN instances
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
|
||||
// Startup VCN instances
|
||||
synchronized (mLock) {
|
||||
mLastSnapshot = snapshot;
|
||||
|
||||
// Start any VCN instances as necessary
|
||||
for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
|
||||
if (snapshot.packageHasPermissionsForSubscriptionGroup(
|
||||
entry.getKey(), entry.getValue().getProvisioningPackageName())) {
|
||||
if (!mVcns.containsKey(entry.getKey())) {
|
||||
startVcnLocked(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
// Cancel any scheduled teardowns for active subscriptions
|
||||
mHandler.removeCallbacksAndMessages(mVcns.get(entry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule teardown of any VCN instances that have lost carrier privileges (after a
|
||||
// delay)
|
||||
for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
|
||||
final VcnConfig config = mConfigs.get(entry.getKey());
|
||||
if (config == null
|
||||
|| !snapshot.packageHasPermissionsForSubscriptionGroup(
|
||||
entry.getKey(), config.getProvisioningPackageName())) {
|
||||
final ParcelUuid uuidToTeardown = entry.getKey();
|
||||
final Vcn instanceToTeardown = entry.getValue();
|
||||
|
||||
mHandler.postDelayed(() -> {
|
||||
synchronized (mLock) {
|
||||
// Guard against case where this is run after a old instance was
|
||||
// torn down, and a new instance was started. Verify to ensure
|
||||
// correct instance is torn down. This could happen as a result of a
|
||||
// Carrier App manually removing/adding a VcnConfig.
|
||||
if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
|
||||
mVcns.remove(uuidToTeardown).teardownAsynchronously();
|
||||
}
|
||||
}
|
||||
}, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
|
||||
Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup);
|
||||
|
||||
// TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
|
||||
// VCN.
|
||||
|
||||
final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config);
|
||||
mVcns.put(subscriptionGroup, newInstance);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void startOrUpdateVcnLocked(
|
||||
@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
|
||||
Slog.v(TAG, "Starting or updating VCN config for subGrp: " + subscriptionGroup);
|
||||
|
||||
if (mVcns.containsKey(subscriptionGroup)) {
|
||||
mVcns.get(subscriptionGroup).updateConfig(config);
|
||||
} else {
|
||||
startVcnLocked(subscriptionGroup, config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a VCN config for a given subscription group.
|
||||
*
|
||||
* <p>Implements the IVcnManagementService Binder interface.
|
||||
*/
|
||||
@Override
|
||||
public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
|
||||
public void setVcnConfig(
|
||||
@NonNull ParcelUuid subscriptionGroup,
|
||||
@NonNull VcnConfig config,
|
||||
@NonNull String opPkgName) {
|
||||
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
|
||||
requireNonNull(config, "config was null");
|
||||
requireNonNull(opPkgName, "opPkgName was null");
|
||||
if (!config.getProvisioningPackageName().equals(opPkgName)) {
|
||||
throw new IllegalArgumentException("Mismatched caller and VcnConfig creator");
|
||||
}
|
||||
Slog.v(TAG, "VCN config updated for subGrp: " + subscriptionGroup);
|
||||
|
||||
mContext.getSystemService(AppOpsManager.class)
|
||||
.checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
|
||||
enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
|
||||
|
||||
synchronized (mLock) {
|
||||
mConfigs.put(subscriptionGroup, config);
|
||||
Binder.withCleanCallingIdentity(() -> {
|
||||
synchronized (mLock) {
|
||||
mConfigs.put(subscriptionGroup, config);
|
||||
startOrUpdateVcnLocked(subscriptionGroup, config);
|
||||
|
||||
// Must be done synchronously to ensure that writes do not happen out-of-order.
|
||||
writeConfigsToDiskLocked();
|
||||
}
|
||||
|
||||
// TODO: Clear Binder calling identity
|
||||
// TODO: Trigger startup as necessary
|
||||
writeConfigsToDiskLocked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,18 +448,21 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
@Override
|
||||
public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
|
||||
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
|
||||
Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup);
|
||||
|
||||
enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
|
||||
|
||||
synchronized (mLock) {
|
||||
mConfigs.remove(subscriptionGroup);
|
||||
Binder.withCleanCallingIdentity(() -> {
|
||||
synchronized (mLock) {
|
||||
mConfigs.remove(subscriptionGroup);
|
||||
|
||||
// Must be done synchronously to ensure that writes do not happen out-of-order.
|
||||
writeConfigsToDiskLocked();
|
||||
}
|
||||
if (mVcns.containsKey(subscriptionGroup)) {
|
||||
mVcns.remove(subscriptionGroup).teardownAsynchronously();
|
||||
}
|
||||
|
||||
// TODO: Clear Binder calling identity
|
||||
// TODO: Trigger teardown as necessary
|
||||
writeConfigsToDiskLocked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@@ -345,19 +488,11 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Network provider for VCN networks.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class VcnNetworkProvider extends NetworkProvider {
|
||||
VcnNetworkProvider(Context context, Looper looper) {
|
||||
super(context, looper, VcnNetworkProvider.class.getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
|
||||
// TODO: Handle network requests - Ensure VCN started, and start appropriate tunnels.
|
||||
/** Get current configuration list for testing purposes */
|
||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||
public Map<ParcelUuid, Vcn> getAllVcns() {
|
||||
synchronized (mLock) {
|
||||
return Collections.unmodifiableMap(mVcns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ import android.telephony.CarrierConfigManager;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
|
||||
@@ -79,6 +81,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
||||
@NonNull private final TelephonySubscriptionTrackerCallback mCallback;
|
||||
@NonNull private final Dependencies mDeps;
|
||||
|
||||
@NonNull private final TelephonyManager mTelephonyManager;
|
||||
@NonNull private final SubscriptionManager mSubscriptionManager;
|
||||
@NonNull private final CarrierConfigManager mCarrierConfigManager;
|
||||
|
||||
@@ -106,6 +109,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
||||
mCallback = Objects.requireNonNull(callback, "Missing callback");
|
||||
mDeps = Objects.requireNonNull(deps, "Missing deps");
|
||||
|
||||
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
|
||||
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
|
||||
mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
|
||||
|
||||
@@ -139,7 +143,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
||||
* so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking.
|
||||
*/
|
||||
public void handleSubscriptionsChanged() {
|
||||
final Set<ParcelUuid> activeSubGroups = new ArraySet<>();
|
||||
final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>();
|
||||
final Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>();
|
||||
|
||||
final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList();
|
||||
@@ -166,12 +170,22 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
||||
// group.
|
||||
if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX
|
||||
&& mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) {
|
||||
activeSubGroups.add(subInfo.getGroupUuid());
|
||||
// TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker
|
||||
|
||||
final TelephonyManager subIdSpecificTelephonyManager =
|
||||
mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId());
|
||||
|
||||
final ParcelUuid subGroup = subInfo.getGroupUuid();
|
||||
final Set<String> pkgs =
|
||||
privilegedPackages.getOrDefault(subGroup, new ArraySet<>());
|
||||
pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges());
|
||||
|
||||
privilegedPackages.put(subGroup, pkgs);
|
||||
}
|
||||
}
|
||||
|
||||
final TelephonySubscriptionSnapshot newSnapshot =
|
||||
new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups);
|
||||
new TelephonySubscriptionSnapshot(newSubIdToGroupMap, privilegedPackages);
|
||||
|
||||
// If snapshot was meaningfully updated, fire the callback
|
||||
if (!newSnapshot.equals(mCurrentSnapshot)) {
|
||||
@@ -231,22 +245,40 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
||||
/** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */
|
||||
public static class TelephonySubscriptionSnapshot {
|
||||
private final Map<Integer, ParcelUuid> mSubIdToGroupMap;
|
||||
private final Set<ParcelUuid> mActiveGroups;
|
||||
private final Map<ParcelUuid, Set<String>> mPrivilegedPackages;
|
||||
|
||||
public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
|
||||
new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptyMap());
|
||||
|
||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||
TelephonySubscriptionSnapshot(
|
||||
@NonNull Map<Integer, ParcelUuid> subIdToGroupMap,
|
||||
@NonNull Set<ParcelUuid> activeGroups) {
|
||||
mSubIdToGroupMap = Collections.unmodifiableMap(
|
||||
Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"));
|
||||
mActiveGroups = Collections.unmodifiableSet(
|
||||
Objects.requireNonNull(activeGroups, "activeGroups was null"));
|
||||
@NonNull Map<ParcelUuid, Set<String>> privilegedPackages) {
|
||||
Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null");
|
||||
Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");
|
||||
|
||||
mSubIdToGroupMap = Collections.unmodifiableMap(subIdToGroupMap);
|
||||
|
||||
final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>();
|
||||
for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) {
|
||||
unmodifiableInnerSets.put(
|
||||
entry.getKey(), Collections.unmodifiableSet(entry.getValue()));
|
||||
}
|
||||
mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets);
|
||||
}
|
||||
|
||||
/** Returns the active subscription groups */
|
||||
@NonNull
|
||||
public Set<ParcelUuid> getActiveSubscriptionGroups() {
|
||||
return mActiveGroups;
|
||||
return mPrivilegedPackages.keySet();
|
||||
}
|
||||
|
||||
/** Checks if the provided package is carrier privileged for the specified sub group. */
|
||||
public boolean packageHasPermissionsForSubscriptionGroup(
|
||||
@NonNull ParcelUuid subGrp, @NonNull String packageName) {
|
||||
final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp);
|
||||
|
||||
return privilegedPackages != null && privilegedPackages.contains(packageName);
|
||||
}
|
||||
|
||||
/** Returns the Subscription Group for a given subId. */
|
||||
@@ -273,7 +305,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mSubIdToGroupMap, mActiveGroups);
|
||||
return Objects.hash(mSubIdToGroupMap, mPrivilegedPackages);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -285,7 +317,15 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
||||
final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;
|
||||
|
||||
return mSubIdToGroupMap.equals(other.mSubIdToGroupMap)
|
||||
&& mActiveGroups.equals(other.mActiveGroups);
|
||||
&& mPrivilegedPackages.equals(other.mPrivilegedPackages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TelephonySubscriptionSnapshot{ "
|
||||
+ "mSubIdToGroupMap=" + mSubIdToGroupMap
|
||||
+ ", mPrivilegedPackages=" + mPrivilegedPackages
|
||||
+ " }";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,32 +16,69 @@
|
||||
|
||||
package com.android.server.vcn;
|
||||
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.vcn.VcnConfig;
|
||||
import android.net.vcn.VcnGatewayConnectionConfig;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelUuid;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents an single instance of a VCN.
|
||||
*
|
||||
* <p>Each Vcn instance manages all tunnels for a given subscription group, including per-capability
|
||||
* networks, network selection, and multi-homing.
|
||||
* <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group,
|
||||
* including per-capability networks, network selection, and multi-homing.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class Vcn extends Handler {
|
||||
private static final String TAG = Vcn.class.getSimpleName();
|
||||
|
||||
private static final int MSG_EVENT_BASE = 0;
|
||||
private static final int MSG_CMD_BASE = 100;
|
||||
|
||||
/**
|
||||
* A carrier app updated the configuration.
|
||||
*
|
||||
* <p>Triggers update of config, re-evaluating all active and underlying networks.
|
||||
*
|
||||
* @param obj VcnConfig
|
||||
*/
|
||||
private static final int MSG_EVENT_CONFIG_UPDATED = MSG_EVENT_BASE;
|
||||
|
||||
/**
|
||||
* A NetworkRequest was added or updated.
|
||||
*
|
||||
* <p>Triggers an evaluation of all active networks, bringing up a new one if necessary.
|
||||
*
|
||||
* @param obj NetworkRequest
|
||||
*/
|
||||
private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1;
|
||||
|
||||
/** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
|
||||
private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
|
||||
|
||||
@NonNull private final VcnContext mVcnContext;
|
||||
@NonNull private final ParcelUuid mSubscriptionGroup;
|
||||
@NonNull private final Dependencies mDeps;
|
||||
@NonNull private final VcnNetworkRequestListener mRequestListener;
|
||||
|
||||
@NonNull
|
||||
private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
|
||||
new HashMap<>();
|
||||
|
||||
@NonNull private VcnConfig mConfig;
|
||||
|
||||
private boolean mIsRunning = true;
|
||||
|
||||
public Vcn(
|
||||
@NonNull VcnContext vcnContext,
|
||||
@NonNull ParcelUuid subscriptionGroup,
|
||||
@@ -58,31 +95,123 @@ public class Vcn extends Handler {
|
||||
mVcnContext = vcnContext;
|
||||
mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
|
||||
mDeps = Objects.requireNonNull(deps, "Missing deps");
|
||||
mRequestListener = new VcnNetworkRequestListener();
|
||||
|
||||
mConfig = Objects.requireNonNull(config, "Missing config");
|
||||
|
||||
// Register to receive cached and future NetworkRequests
|
||||
mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
|
||||
}
|
||||
|
||||
/** Asynchronously updates the configuration and triggers a re-evaluation of Networks */
|
||||
public void updateConfig(@NonNull VcnConfig config) {
|
||||
Objects.requireNonNull(config, "Missing config");
|
||||
// TODO: Proxy to handler, and make config there.
|
||||
|
||||
sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config));
|
||||
}
|
||||
|
||||
/** Asynchronously tears down this Vcn instance, along with all tunnels and Networks */
|
||||
public void teardown() {
|
||||
// TODO: Proxy to handler, and teardown there.
|
||||
/** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */
|
||||
public void teardownAsynchronously() {
|
||||
sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
|
||||
}
|
||||
|
||||
/** Notifies this Vcn instance of a new NetworkRequest */
|
||||
public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
|
||||
Objects.requireNonNull(request, "Missing request");
|
||||
private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
|
||||
@Override
|
||||
public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
|
||||
Objects.requireNonNull(request, "Missing request");
|
||||
|
||||
// TODO: Proxy to handler, and handle there.
|
||||
sendMessage(obtainMessage(MSG_EVENT_NETWORK_REQUESTED, score, providerId, request));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(@NonNull Message msg) {
|
||||
// TODO: Do something
|
||||
if (!mIsRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.what) {
|
||||
case MSG_EVENT_CONFIG_UPDATED:
|
||||
handleConfigUpdated((VcnConfig) msg.obj);
|
||||
break;
|
||||
case MSG_EVENT_NETWORK_REQUESTED:
|
||||
handleNetworkRequested((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
|
||||
break;
|
||||
case MSG_CMD_TEARDOWN:
|
||||
handleTeardown();
|
||||
break;
|
||||
default:
|
||||
Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleConfigUpdated(@NonNull VcnConfig config) {
|
||||
// TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode()
|
||||
Slog.v(getLogTag(), String.format("Config updated: config = %s", config.hashCode()));
|
||||
|
||||
mConfig = config;
|
||||
|
||||
// TODO: Reevaluate active VcnGatewayConnection(s)
|
||||
}
|
||||
|
||||
private void handleTeardown() {
|
||||
mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener);
|
||||
|
||||
for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
|
||||
gatewayConnection.teardownAsynchronously();
|
||||
}
|
||||
|
||||
mIsRunning = false;
|
||||
}
|
||||
|
||||
private void handleNetworkRequested(
|
||||
@NonNull NetworkRequest request, int score, int providerId) {
|
||||
if (score > getNetworkScore()) {
|
||||
Slog.v(getLogTag(),
|
||||
"Request " + request.requestId + " already satisfied by higher-scoring ("
|
||||
+ score + ") network from provider " + providerId);
|
||||
return;
|
||||
}
|
||||
|
||||
// If preexisting VcnGatewayConnection(s) satisfy request, return
|
||||
for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
|
||||
if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
|
||||
Slog.v(getLogTag(),
|
||||
"Request " + request.requestId
|
||||
+ " satisfied by existing VcnGatewayConnection");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it
|
||||
// up
|
||||
for (VcnGatewayConnectionConfig gatewayConnectionConfig :
|
||||
mConfig.getGatewayConnectionConfigs()) {
|
||||
if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
|
||||
Slog.v(
|
||||
getLogTag(),
|
||||
"Bringing up new VcnGatewayConnection for request " + request.requestId);
|
||||
|
||||
final VcnGatewayConnection vcnGatewayConnection =
|
||||
new VcnGatewayConnection(
|
||||
mVcnContext, mSubscriptionGroup, gatewayConnectionConfig);
|
||||
mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean requestSatisfiedByGatewayConnectionConfig(
|
||||
@NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
|
||||
final NetworkCapabilities configCaps = new NetworkCapabilities();
|
||||
for (int cap : config.getAllExposedCapabilities()) {
|
||||
configCaps.addCapability(cap);
|
||||
}
|
||||
|
||||
return request.networkCapabilities.satisfiedByNetworkCapabilities(configCaps);
|
||||
}
|
||||
|
||||
private String getLogTag() {
|
||||
return String.format("%s [%d]", TAG, mSubscriptionGroup.hashCode());
|
||||
}
|
||||
|
||||
/** Retrieves the network score for a VCN Network */
|
||||
|
||||
@@ -20,8 +20,6 @@ import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.android.server.VcnManagementService.VcnNetworkProvider;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,8 +65,8 @@ public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTr
|
||||
mDeps.newUnderlyingNetworkTracker(mVcnContext, subscriptionGroup, this);
|
||||
}
|
||||
|
||||
/** Tears down this GatewayConnection, and any resources used */
|
||||
public void teardown() {
|
||||
/** Asynchronously tears down this GatewayConnection, and any resources used */
|
||||
public void teardownAsynchronously() {
|
||||
mUnderlyingNetworkTracker.teardown();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.vcn;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.net.NetworkProvider;
|
||||
import android.net.NetworkRequest;
|
||||
import android.os.Looper;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* VCN Network Provider routes NetworkRequests to listeners to bring up tunnels as needed.
|
||||
*
|
||||
* <p>The VcnNetworkProvider provides a caching layer to ensure that all listeners receive all
|
||||
* active NetworkRequest(s), including ones that were filed prior to listener registration.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class VcnNetworkProvider extends NetworkProvider {
|
||||
private static final String TAG = VcnNetworkProvider.class.getSimpleName();
|
||||
|
||||
private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
|
||||
private final SparseArray<NetworkRequestEntry> mRequests = new SparseArray<>();
|
||||
|
||||
public VcnNetworkProvider(Context context, Looper looper) {
|
||||
super(context, looper, VcnNetworkProvider.class.getSimpleName());
|
||||
}
|
||||
|
||||
// Package-private
|
||||
void registerListener(@NonNull NetworkRequestListener listener) {
|
||||
mListeners.add(listener);
|
||||
|
||||
// Send listener all cached requests
|
||||
for (int i = 0; i < mRequests.size(); i++) {
|
||||
notifyListenerForEvent(listener, mRequests.valueAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Package-private
|
||||
void unregisterListener(@NonNull NetworkRequestListener listener) {
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
|
||||
private void notifyListenerForEvent(
|
||||
@NonNull NetworkRequestListener listener, @NonNull NetworkRequestEntry entry) {
|
||||
listener.onNetworkRequested(entry.mRequest, entry.mScore, entry.mProviderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
|
||||
Slog.v(
|
||||
TAG,
|
||||
String.format(
|
||||
"Network requested: Request = %s, score = %d, providerId = %d",
|
||||
request, score, providerId));
|
||||
|
||||
final NetworkRequestEntry entry = new NetworkRequestEntry(request, score, providerId);
|
||||
mRequests.put(request.requestId, entry);
|
||||
|
||||
// TODO(b/176939047): Intelligently route requests to prioritized VcnInstances (based on
|
||||
// Default Data Sub, or similar)
|
||||
for (NetworkRequestListener listener : mListeners) {
|
||||
notifyListenerForEvent(listener, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
|
||||
mRequests.remove(request.requestId);
|
||||
}
|
||||
|
||||
private static class NetworkRequestEntry {
|
||||
public final NetworkRequest mRequest;
|
||||
public final int mScore;
|
||||
public final int mProviderId;
|
||||
|
||||
private NetworkRequestEntry(@NonNull NetworkRequest request, int score, int providerId) {
|
||||
mRequest = Objects.requireNonNull(request, "Missing request");
|
||||
mScore = score;
|
||||
mProviderId = providerId;
|
||||
}
|
||||
}
|
||||
|
||||
// package-private
|
||||
interface NetworkRequestListener {
|
||||
void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId);
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,17 @@ package android.net.vcn;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -33,12 +38,15 @@ import java.util.Set;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class VcnConfigTest {
|
||||
private static final String TEST_PACKAGE_NAME = VcnConfigTest.class.getPackage().getName();
|
||||
private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS =
|
||||
Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig());
|
||||
|
||||
private final Context mContext = mock(Context.class);
|
||||
|
||||
// Public visibility for VcnManagementServiceTest
|
||||
public static VcnConfig buildTestConfig() {
|
||||
VcnConfig.Builder builder = new VcnConfig.Builder();
|
||||
public static VcnConfig buildTestConfig(@NonNull Context context) {
|
||||
VcnConfig.Builder builder = new VcnConfig.Builder(context);
|
||||
|
||||
for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
|
||||
builder.addGatewayConnectionConfig(gatewayConnectionConfig);
|
||||
@@ -47,10 +55,24 @@ public class VcnConfigTest {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
doReturn(TEST_PACKAGE_NAME).when(mContext).getOpPackageName();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderConstructorRequiresContext() {
|
||||
try {
|
||||
new VcnConfig.Builder(null);
|
||||
fail("Expected exception due to null context");
|
||||
} catch (NullPointerException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderRequiresGatewayConnectionConfig() {
|
||||
try {
|
||||
new VcnConfig.Builder().build();
|
||||
new VcnConfig.Builder(mContext).build();
|
||||
fail("Expected exception due to no VcnGatewayConnectionConfigs provided");
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
@@ -58,21 +80,22 @@ public class VcnConfigTest {
|
||||
|
||||
@Test
|
||||
public void testBuilderAndGetters() {
|
||||
final VcnConfig config = buildTestConfig();
|
||||
final VcnConfig config = buildTestConfig(mContext);
|
||||
|
||||
assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
|
||||
assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersistableBundle() {
|
||||
final VcnConfig config = buildTestConfig();
|
||||
final VcnConfig config = buildTestConfig(mContext);
|
||||
|
||||
assertEquals(config, new VcnConfig(config.toPersistableBundle()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParceling() {
|
||||
final VcnConfig config = buildTestConfig();
|
||||
final VcnConfig config = buildTestConfig(mContext);
|
||||
|
||||
Parcel parcel = Parcel.obtain();
|
||||
config.writeToParcel(parcel, 0);
|
||||
|
||||
@@ -16,16 +16,23 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
|
||||
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.argThat;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.vcn.VcnConfig;
|
||||
@@ -42,29 +49,47 @@ import android.telephony.TelephonyManager;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.vcn.TelephonySubscriptionTracker;
|
||||
import com.android.server.vcn.Vcn;
|
||||
import com.android.server.vcn.VcnContext;
|
||||
import com.android.server.vcn.VcnNetworkProvider;
|
||||
import com.android.server.vcn.util.PersistableBundleUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Tests for {@link VcnManagementService}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class VcnManagementServiceTest {
|
||||
private static final String TEST_PACKAGE_NAME =
|
||||
VcnManagementServiceTest.class.getPackage().getName();
|
||||
private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0));
|
||||
private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1));
|
||||
private static final VcnConfig TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig();
|
||||
private static final VcnConfig TEST_VCN_CONFIG;
|
||||
private static final int TEST_UID = Process.FIRST_APPLICATION_UID;
|
||||
|
||||
static {
|
||||
final Context mockConfigContext = mock(Context.class);
|
||||
doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName();
|
||||
|
||||
TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(mockConfigContext);
|
||||
}
|
||||
|
||||
private static final Map<ParcelUuid, VcnConfig> TEST_VCN_CONFIG_MAP =
|
||||
Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG));
|
||||
|
||||
private static final int TEST_SUBSCRIPTION_ID = 1;
|
||||
private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO =
|
||||
new SubscriptionInfo(
|
||||
1 /* id */,
|
||||
TEST_SUBSCRIPTION_ID /* id */,
|
||||
"" /* iccId */,
|
||||
0 /* simSlotIndex */,
|
||||
"Carrier" /* displayName */,
|
||||
@@ -92,22 +117,48 @@ public class VcnManagementServiceTest {
|
||||
private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class);
|
||||
private final TelephonyManager mTelMgr = mock(TelephonyManager.class);
|
||||
private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class);
|
||||
private final VcnManagementService mVcnMgmtSvc;
|
||||
private final AppOpsManager mAppOpsMgr = mock(AppOpsManager.class);
|
||||
private final VcnContext mVcnContext = mock(VcnContext.class);
|
||||
private final PersistableBundleUtils.LockingReadWriteHelper mConfigReadWriteHelper =
|
||||
mock(PersistableBundleUtils.LockingReadWriteHelper.class);
|
||||
private final TelephonySubscriptionTracker mSubscriptionTracker =
|
||||
mock(TelephonySubscriptionTracker.class);
|
||||
|
||||
private final VcnManagementService mVcnMgmtSvc;
|
||||
|
||||
public VcnManagementServiceTest() throws Exception {
|
||||
setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
|
||||
setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class);
|
||||
setupSystemService(
|
||||
mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class);
|
||||
setupSystemService(mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class);
|
||||
|
||||
doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName();
|
||||
|
||||
doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper();
|
||||
doReturn(Process.FIRST_APPLICATION_UID).when(mMockDeps).getBinderCallingUid();
|
||||
doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid();
|
||||
doReturn(mVcnContext)
|
||||
.when(mMockDeps)
|
||||
.newVcnContext(
|
||||
eq(mMockContext),
|
||||
eq(mTestLooper.getLooper()),
|
||||
any(VcnNetworkProvider.class));
|
||||
doReturn(mSubscriptionTracker)
|
||||
.when(mMockDeps)
|
||||
.newTelephonySubscriptionTracker(
|
||||
eq(mMockContext),
|
||||
eq(mTestLooper.getLooper()),
|
||||
any(TelephonySubscriptionTrackerCallback.class));
|
||||
doReturn(mConfigReadWriteHelper)
|
||||
.when(mMockDeps)
|
||||
.newPersistableBundleLockingReadWriteHelper(any());
|
||||
|
||||
// Setup VCN instance generation
|
||||
doAnswer((invocation) -> {
|
||||
// Mock-within a doAnswer is safe, because it doesn't actually run nested.
|
||||
return mock(Vcn.class);
|
||||
}).when(mMockDeps).newVcn(any(), any(), any());
|
||||
|
||||
final PersistableBundle bundle =
|
||||
PersistableBundleUtils.fromMap(
|
||||
TEST_VCN_CONFIG_MAP,
|
||||
@@ -117,6 +168,9 @@ public class VcnManagementServiceTest {
|
||||
|
||||
setupMockedCarrierPrivilege(true);
|
||||
mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
|
||||
|
||||
// Make sure the profiles are loaded.
|
||||
mTestLooper.dispatchAll();
|
||||
}
|
||||
|
||||
private void setupSystemService(Object service, String name, Class<?> serviceClass) {
|
||||
@@ -137,8 +191,8 @@ public class VcnManagementServiceTest {
|
||||
public void testSystemReady() throws Exception {
|
||||
mVcnMgmtSvc.systemReady();
|
||||
|
||||
verify(mConnMgr)
|
||||
.registerNetworkProvider(any(VcnManagementService.VcnNetworkProvider.class));
|
||||
verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class));
|
||||
verify(mSubscriptionTracker).register();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -171,12 +225,110 @@ public class VcnManagementServiceTest {
|
||||
verify(mConfigReadWriteHelper).readFromDisk();
|
||||
}
|
||||
|
||||
private void triggerSubscriptionTrackerCallback(Set<ParcelUuid> activeSubscriptionGroups) {
|
||||
final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
|
||||
doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
|
||||
|
||||
final Set<String> privilegedPackages =
|
||||
(activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty())
|
||||
? Collections.emptySet()
|
||||
: Collections.singleton(TEST_PACKAGE_NAME);
|
||||
doReturn(true)
|
||||
.when(snapshot)
|
||||
.packageHasPermissionsForSubscriptionGroup(
|
||||
argThat(val -> activeSubscriptionGroups.contains(val)),
|
||||
eq(TEST_PACKAGE_NAME));
|
||||
|
||||
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
|
||||
cb.onNewSnapshot(snapshot);
|
||||
}
|
||||
|
||||
private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() {
|
||||
final ArgumentCaptor<TelephonySubscriptionTrackerCallback> captor =
|
||||
ArgumentCaptor.forClass(TelephonySubscriptionTrackerCallback.class);
|
||||
verify(mMockDeps)
|
||||
.newTelephonySubscriptionTracker(
|
||||
eq(mMockContext), eq(mTestLooper.getLooper()), captor.capture());
|
||||
return captor.getValue();
|
||||
}
|
||||
|
||||
private Vcn startAndGetVcnInstance(ParcelUuid uuid) {
|
||||
mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
|
||||
return mVcnMgmtSvc.getAllVcns().get(uuid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception {
|
||||
triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1));
|
||||
verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception {
|
||||
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
|
||||
final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
|
||||
|
||||
triggerSubscriptionTrackerCallback(Collections.emptySet());
|
||||
|
||||
// Verify teardown after delay
|
||||
mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(vcn).teardownAsynchronously();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances()
|
||||
throws Exception {
|
||||
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
|
||||
final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
|
||||
|
||||
// Simulate SIM unloaded
|
||||
triggerSubscriptionTrackerCallback(Collections.emptySet());
|
||||
|
||||
// Simulate new SIM loaded right during teardown delay.
|
||||
mTestLooper.moveTimeForward(
|
||||
VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
|
||||
mTestLooper.dispatchAll();
|
||||
triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_2));
|
||||
|
||||
// Verify that even after the full timeout duration, the VCN instance is not torn down
|
||||
mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(vcn, never()).teardownAsynchronously();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception {
|
||||
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
|
||||
final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
|
||||
|
||||
// Simulate SIM unloaded
|
||||
triggerSubscriptionTrackerCallback(Collections.emptySet());
|
||||
|
||||
// Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
|
||||
// vcnInstance.
|
||||
mTestLooper.moveTimeForward(
|
||||
VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
|
||||
mTestLooper.dispatchAll();
|
||||
mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
|
||||
final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2);
|
||||
|
||||
// Verify that new instance was different, and the old one was torn down
|
||||
assertTrue(oldInstance != newInstance);
|
||||
verify(oldInstance).teardownAsynchronously();
|
||||
|
||||
// Verify that even after the full timeout duration, the new VCN instance is not torn down
|
||||
mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(newInstance, never()).teardownAsynchronously();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetVcnConfigRequiresNonSystemServer() throws Exception {
|
||||
doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
|
||||
|
||||
try {
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig());
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
|
||||
fail("Expected IllegalStateException exception for system server");
|
||||
} catch (IllegalStateException expected) {
|
||||
}
|
||||
@@ -184,12 +336,12 @@ public class VcnManagementServiceTest {
|
||||
|
||||
@Test
|
||||
public void testSetVcnConfigRequiresSystemUser() throws Exception {
|
||||
doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID))
|
||||
doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID))
|
||||
.when(mMockDeps)
|
||||
.getBinderCallingUid();
|
||||
|
||||
try {
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig());
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
|
||||
fail("Expected security exception for non system user");
|
||||
} catch (SecurityException expected) {
|
||||
}
|
||||
@@ -200,16 +352,25 @@ public class VcnManagementServiceTest {
|
||||
setupMockedCarrierPrivilege(false);
|
||||
|
||||
try {
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig());
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
|
||||
fail("Expected security exception for missing carrier privileges");
|
||||
} catch (SecurityException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetVcnConfigMismatchedPackages() throws Exception {
|
||||
try {
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage");
|
||||
fail("Expected exception due to mismatched packages in config and method call");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetVcnConfig() throws Exception {
|
||||
// Use a different UUID to simulate a new VCN config.
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG);
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
|
||||
assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2));
|
||||
verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
|
||||
}
|
||||
@@ -227,7 +388,7 @@ public class VcnManagementServiceTest {
|
||||
|
||||
@Test
|
||||
public void testClearVcnConfigRequiresSystemUser() throws Exception {
|
||||
doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID))
|
||||
doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID))
|
||||
.when(mMockDeps)
|
||||
.getBinderCallingUid();
|
||||
|
||||
@@ -255,4 +416,26 @@ public class VcnManagementServiceTest {
|
||||
assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
|
||||
verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetVcnConfigClearVcnConfigStartsUpdatesAndTeardsDownVcns() throws Exception {
|
||||
// Use a different UUID to simulate a new VCN config.
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
|
||||
final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns();
|
||||
final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2);
|
||||
assertEquals(1, vcnInstances.size());
|
||||
assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2));
|
||||
verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
|
||||
|
||||
// Verify Vcn is started
|
||||
verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG));
|
||||
|
||||
// Verify Vcn is updated if it was previously started
|
||||
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
|
||||
verify(vcnInstance).updateConfig(TEST_VCN_CONFIG);
|
||||
|
||||
// Verify Vcn is stopped if it was already started
|
||||
mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
|
||||
verify(vcnInstance).teardownAsynchronously();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
@@ -37,6 +38,10 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -49,6 +54,7 @@ import android.telephony.CarrierConfigManager;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -63,6 +69,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -71,12 +78,16 @@ import java.util.UUID;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class TelephonySubscriptionTrackerTest {
|
||||
private static final String PACKAGE_NAME =
|
||||
TelephonySubscriptionTrackerTest.class.getPackage().getName();
|
||||
private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID());
|
||||
private static final int TEST_SIM_SLOT_INDEX = 1;
|
||||
private static final int TEST_SUBSCRIPTION_ID_1 = 2;
|
||||
private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class);
|
||||
private static final int TEST_SUBSCRIPTION_ID_2 = 3;
|
||||
private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class);
|
||||
private static final Map<ParcelUuid, Set<String>> TEST_PRIVILEGED_PACKAGES =
|
||||
Collections.singletonMap(TEST_PARCEL_UUID, Collections.singleton(PACKAGE_NAME));
|
||||
private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP;
|
||||
|
||||
static {
|
||||
@@ -91,6 +102,7 @@ public class TelephonySubscriptionTrackerTest {
|
||||
@NonNull private final Handler mHandler;
|
||||
@NonNull private final TelephonySubscriptionTracker.Dependencies mDeps;
|
||||
|
||||
@NonNull private final TelephonyManager mTelephonyManager;
|
||||
@NonNull private final SubscriptionManager mSubscriptionManager;
|
||||
@NonNull private final CarrierConfigManager mCarrierConfigManager;
|
||||
|
||||
@@ -103,9 +115,15 @@ public class TelephonySubscriptionTrackerTest {
|
||||
mHandler = new Handler(mTestLooper.getLooper());
|
||||
mDeps = mock(TelephonySubscriptionTracker.Dependencies.class);
|
||||
|
||||
mTelephonyManager = mock(TelephonyManager.class);
|
||||
mSubscriptionManager = mock(SubscriptionManager.class);
|
||||
mCarrierConfigManager = mock(CarrierConfigManager.class);
|
||||
|
||||
doReturn(Context.TELEPHONY_SERVICE)
|
||||
.when(mContext)
|
||||
.getSystemServiceName(TelephonyManager.class);
|
||||
doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
|
||||
|
||||
doReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
|
||||
.when(mContext)
|
||||
.getSystemServiceName(SubscriptionManager.class);
|
||||
@@ -140,6 +158,9 @@ public class TelephonySubscriptionTrackerTest {
|
||||
doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2))
|
||||
.when(mSubscriptionManager)
|
||||
.getAllSubscriptionInfoList();
|
||||
|
||||
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
|
||||
setPrivilegedPackagesForMock(Collections.singletonList(PACKAGE_NAME));
|
||||
}
|
||||
|
||||
private IntentFilter getIntentFilter() {
|
||||
@@ -167,13 +188,15 @@ public class TelephonySubscriptionTrackerTest {
|
||||
return intent;
|
||||
}
|
||||
|
||||
private TelephonySubscriptionSnapshot buildExpectedSnapshot(Set<ParcelUuid> activeSubGroups) {
|
||||
return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, activeSubGroups);
|
||||
private TelephonySubscriptionSnapshot buildExpectedSnapshot(
|
||||
Map<ParcelUuid, Set<String>> privilegedPackages) {
|
||||
return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, privilegedPackages);
|
||||
}
|
||||
|
||||
private TelephonySubscriptionSnapshot buildExpectedSnapshot(
|
||||
Map<Integer, ParcelUuid> subIdToGroupMap, Set<ParcelUuid> activeSubGroups) {
|
||||
return new TelephonySubscriptionSnapshot(subIdToGroupMap, activeSubGroups);
|
||||
Map<Integer, ParcelUuid> subIdToGroupMap,
|
||||
Map<ParcelUuid, Set<String>> privilegedPackages) {
|
||||
return new TelephonySubscriptionSnapshot(subIdToGroupMap, privilegedPackages);
|
||||
}
|
||||
|
||||
private void verifyNoActiveSubscriptions() {
|
||||
@@ -186,6 +209,10 @@ public class TelephonySubscriptionTrackerTest {
|
||||
Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1));
|
||||
}
|
||||
|
||||
private void setPrivilegedPackagesForMock(@NonNull List<String> privilegedPackages) {
|
||||
doReturn(privilegedPackages).when(mTelephonyManager).getPackagesWithCarrierPrivileges();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegister() throws Exception {
|
||||
verify(mContext)
|
||||
@@ -223,15 +250,30 @@ public class TelephonySubscriptionTrackerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSubscriptionsChangedFired_WithReadySubIds() throws Exception {
|
||||
public void testOnSubscriptionsChangedFired_WithReadySubidsNoPrivilegedPackages()
|
||||
throws Exception {
|
||||
setupReadySubIds();
|
||||
setPrivilegedPackagesForMock(Collections.emptyList());
|
||||
|
||||
final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
|
||||
listener.onSubscriptionsChanged();
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
final Map<ParcelUuid, Set<String>> privilegedPackages =
|
||||
Collections.singletonMap(TEST_PARCEL_UUID, new ArraySet<>());
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(privilegedPackages)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSubscriptionsChangedFired_WithReadySubidsAndPrivilegedPackages()
|
||||
throws Exception {
|
||||
setupReadySubIds();
|
||||
|
||||
final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
|
||||
listener.onSubscriptionsChanged();
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -239,8 +281,7 @@ public class TelephonySubscriptionTrackerTest {
|
||||
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -253,8 +294,7 @@ public class TelephonySubscriptionTrackerTest {
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Expect an empty snapshot
|
||||
verify(mCallback).onNewSnapshot(
|
||||
eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet())));
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -281,41 +321,57 @@ public class TelephonySubscriptionTrackerTest {
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception {
|
||||
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
|
||||
|
||||
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
|
||||
assertNotNull(
|
||||
mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
|
||||
|
||||
doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList();
|
||||
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mCallback).onNewSnapshot(
|
||||
eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet())));
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSlotClearedAfterValidTriggersCallbacks() throws Exception {
|
||||
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
|
||||
|
||||
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
|
||||
assertNotNull(
|
||||
mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
|
||||
|
||||
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false));
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(Collections.emptySet())));
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap())));
|
||||
assertNull(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangingPrivilegedPackagesAfterValidTriggersCallbacks() throws Exception {
|
||||
setupReadySubIds();
|
||||
|
||||
// Setup initial "valid" state
|
||||
final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
|
||||
listener.onSubscriptionsChanged();
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
|
||||
|
||||
// Simulate a loss of carrier privileges
|
||||
setPrivilegedPackagesForMock(Collections.emptyList());
|
||||
listener.onSubscriptionsChanged();
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mCallback)
|
||||
.onNewSnapshot(
|
||||
eq(buildExpectedSnapshot(singletonMap(TEST_PARCEL_UUID, emptySet()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception {
|
||||
final TelephonySubscriptionSnapshot snapshot =
|
||||
new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet());
|
||||
new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, emptyMap());
|
||||
|
||||
assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_1));
|
||||
assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2));
|
||||
@@ -324,7 +380,7 @@ public class TelephonySubscriptionTrackerTest {
|
||||
@Test
|
||||
public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception {
|
||||
final TelephonySubscriptionSnapshot snapshot =
|
||||
new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet());
|
||||
new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, emptyMap());
|
||||
|
||||
assertEquals(
|
||||
new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)),
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.vcn;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.os.test.TestLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Tests for TelephonySubscriptionTracker */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class VcnNetworkProviderTest {
|
||||
private static final int TEST_SCORE_UNSATISFIED = 0;
|
||||
private static final int TEST_SCORE_HIGH = 100;
|
||||
private static final int TEST_PROVIDER_ID = 1;
|
||||
private static final int TEST_LEGACY_TYPE = ConnectivityManager.TYPE_MOBILE;
|
||||
private static final NetworkRequest.Type TEST_REQUEST_TYPE = NetworkRequest.Type.REQUEST;
|
||||
|
||||
@NonNull private final Context mContext;
|
||||
@NonNull private final TestLooper mTestLooper;
|
||||
|
||||
@NonNull private VcnNetworkProvider mVcnNetworkProvider;
|
||||
@NonNull private NetworkRequestListener mListener;
|
||||
|
||||
public VcnNetworkProviderTest() {
|
||||
mContext = mock(Context.class);
|
||||
mTestLooper = new TestLooper();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mVcnNetworkProvider = new VcnNetworkProvider(mContext, mTestLooper.getLooper());
|
||||
mListener = mock(NetworkRequestListener.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestsPassedToRegisteredListeners() throws Exception {
|
||||
mVcnNetworkProvider.registerListener(mListener);
|
||||
|
||||
final NetworkRequest request = mock(NetworkRequest.class);
|
||||
mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID);
|
||||
verify(mListener).onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestsPassedToRegisteredListeners_satisfiedByHighScoringProvider()
|
||||
throws Exception {
|
||||
mVcnNetworkProvider.registerListener(mListener);
|
||||
|
||||
final NetworkRequest request = mock(NetworkRequest.class);
|
||||
mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_HIGH, TEST_PROVIDER_ID);
|
||||
verify(mListener).onNetworkRequested(request, TEST_SCORE_HIGH, TEST_PROVIDER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregisterListener() throws Exception {
|
||||
mVcnNetworkProvider.registerListener(mListener);
|
||||
mVcnNetworkProvider.unregisterListener(mListener);
|
||||
|
||||
final NetworkRequest request = mock(NetworkRequest.class);
|
||||
mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID);
|
||||
verifyNoMoreInteractions(mListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCachedRequestsPassedOnRegister() throws Exception {
|
||||
final List<NetworkRequest> requests = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
final NetworkRequest request =
|
||||
new NetworkRequest(
|
||||
new NetworkCapabilities(),
|
||||
TEST_LEGACY_TYPE,
|
||||
i /* requestId */,
|
||||
TEST_REQUEST_TYPE);
|
||||
|
||||
requests.add(request);
|
||||
mVcnNetworkProvider.onNetworkRequested(request, i, i + 1);
|
||||
}
|
||||
|
||||
mVcnNetworkProvider.registerListener(mListener);
|
||||
for (int i = 0; i < requests.size(); i++) {
|
||||
final NetworkRequest request = requests.get(i);
|
||||
verify(mListener).onNetworkRequested(request, i, i + 1);
|
||||
}
|
||||
verifyNoMoreInteractions(mListener);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user