Merge changes I3b769562,I63203188,Ia9396d66,Ifec34fad am: 20bfe66a02 am: f2d8cfe88c
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1515262 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: Ie5b8736bcb1fb7010b97512e6970158373337a1e
This commit is contained in:
@@ -23,6 +23,6 @@ import android.os.ParcelUuid;
|
|||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
interface IVcnManagementService {
|
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);
|
void clearVcnConfig(in ParcelUuid subscriptionGroup);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility;
|
|||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.os.PersistableBundle;
|
import android.os.PersistableBundle;
|
||||||
@@ -45,11 +46,17 @@ import java.util.Set;
|
|||||||
public final class VcnConfig implements Parcelable {
|
public final class VcnConfig implements Parcelable {
|
||||||
@NonNull private static final String TAG = VcnConfig.class.getSimpleName();
|
@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";
|
private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
|
||||||
@NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
|
@NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
|
||||||
|
|
||||||
private VcnConfig(@NonNull Set<VcnGatewayConnectionConfig> tunnelConfigs) {
|
private VcnConfig(
|
||||||
mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs);
|
@NonNull String packageName,
|
||||||
|
@NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
|
||||||
|
mPackageName = packageName;
|
||||||
|
mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs);
|
||||||
|
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
@@ -61,6 +68,8 @@ public final class VcnConfig implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||||
public VcnConfig(@NonNull PersistableBundle in) {
|
public VcnConfig(@NonNull PersistableBundle in) {
|
||||||
|
mPackageName = in.getString(PACKAGE_NAME_KEY);
|
||||||
|
|
||||||
final PersistableBundle gatewayConnectionConfigsBundle =
|
final PersistableBundle gatewayConnectionConfigsBundle =
|
||||||
in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY);
|
in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY);
|
||||||
mGatewayConnectionConfigs =
|
mGatewayConnectionConfigs =
|
||||||
@@ -72,8 +81,19 @@ public final class VcnConfig implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validate() {
|
private void validate() {
|
||||||
|
Objects.requireNonNull(mPackageName, "packageName was null");
|
||||||
Preconditions.checkCollectionNotEmpty(
|
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. */
|
/** Retrieves the set of configured tunnels. */
|
||||||
@@ -91,6 +111,8 @@ public final class VcnConfig implements Parcelable {
|
|||||||
public PersistableBundle toPersistableBundle() {
|
public PersistableBundle toPersistableBundle() {
|
||||||
final PersistableBundle result = new PersistableBundle();
|
final PersistableBundle result = new PersistableBundle();
|
||||||
|
|
||||||
|
result.putString(PACKAGE_NAME_KEY, mPackageName);
|
||||||
|
|
||||||
final PersistableBundle gatewayConnectionConfigsBundle =
|
final PersistableBundle gatewayConnectionConfigsBundle =
|
||||||
PersistableBundleUtils.fromList(
|
PersistableBundleUtils.fromList(
|
||||||
new ArrayList<>(mGatewayConnectionConfigs),
|
new ArrayList<>(mGatewayConnectionConfigs),
|
||||||
@@ -102,7 +124,7 @@ public final class VcnConfig implements Parcelable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(mGatewayConnectionConfigs);
|
return Objects.hash(mPackageName, mGatewayConnectionConfigs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -112,7 +134,8 @@ public final class VcnConfig implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final VcnConfig rhs = (VcnConfig) other;
|
final VcnConfig rhs = (VcnConfig) other;
|
||||||
return mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
|
return mPackageName.equals(rhs.mPackageName)
|
||||||
|
&& mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parcelable methods
|
// Parcelable methods
|
||||||
@@ -143,9 +166,17 @@ public final class VcnConfig implements Parcelable {
|
|||||||
|
|
||||||
/** This class is used to incrementally build {@link VcnConfig} objects. */
|
/** This class is used to incrementally build {@link VcnConfig} objects. */
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
@NonNull private final String mPackageName;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
|
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.
|
* Adds a configuration for an individual gateway connection.
|
||||||
*
|
*
|
||||||
@@ -168,7 +199,7 @@ public final class VcnConfig implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public VcnConfig build() {
|
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");
|
requireNonNull(config, "config was null");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mService.setVcnConfig(subscriptionGroup, config);
|
mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName());
|
||||||
} catch (ServiceSpecificException e) {
|
} catch (ServiceSpecificException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
|
|||||||
@@ -16,13 +16,15 @@
|
|||||||
|
|
||||||
package com.android.server;
|
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 static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.app.AppOpsManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkProvider;
|
|
||||||
import android.net.NetworkRequest;
|
|
||||||
import android.net.vcn.IVcnManagementService;
|
import android.net.vcn.IVcnManagementService;
|
||||||
import android.net.vcn.VcnConfig;
|
import android.net.vcn.VcnConfig;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
@@ -43,6 +45,10 @@ import android.util.Slog;
|
|||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
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.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 com.android.server.vcn.util.PersistableBundleUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -51,6 +57,7 @@ import java.util.Collections;
|
|||||||
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.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
|
* VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
|
||||||
@@ -115,6 +122,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
|||||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||||
static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml";
|
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 */
|
/* Binder context for this service */
|
||||||
@NonNull private final Context mContext;
|
@NonNull private final Context mContext;
|
||||||
@NonNull private final Dependencies mDeps;
|
@NonNull private final Dependencies mDeps;
|
||||||
@@ -122,11 +133,23 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
|||||||
@NonNull private final Looper mLooper;
|
@NonNull private final Looper mLooper;
|
||||||
@NonNull private final Handler mHandler;
|
@NonNull private final Handler mHandler;
|
||||||
@NonNull private final VcnNetworkProvider mNetworkProvider;
|
@NonNull private final VcnNetworkProvider mNetworkProvider;
|
||||||
|
@NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
|
||||||
|
@NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
|
||||||
|
@NonNull private final VcnContext mVcnContext;
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();
|
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 Object mLock = new Object();
|
||||||
|
|
||||||
@NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper;
|
@NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper;
|
||||||
@@ -139,8 +162,12 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
|||||||
mLooper = mDeps.getLooper();
|
mLooper = mDeps.getLooper();
|
||||||
mHandler = new Handler(mLooper);
|
mHandler = new Handler(mLooper);
|
||||||
mNetworkProvider = new VcnNetworkProvider(mContext, mLooper);
|
mNetworkProvider = new VcnNetworkProvider(mContext, mLooper);
|
||||||
|
mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback();
|
||||||
|
mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker(
|
||||||
|
mContext, mLooper, mTelephonySubscriptionTrackerCb);
|
||||||
|
|
||||||
mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
|
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
|
// Run on handler to ensure I/O does not block system server startup
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> {
|
||||||
@@ -174,7 +201,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
|||||||
mConfigs.put(entry.getKey(), entry.getValue());
|
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();
|
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
|
* Retrieves the caller's UID
|
||||||
*
|
*
|
||||||
@@ -225,12 +263,29 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
|||||||
newPersistableBundleLockingReadWriteHelper(@NonNull String path) {
|
newPersistableBundleLockingReadWriteHelper(@NonNull String path) {
|
||||||
return new PersistableBundleUtils.LockingReadWriteHelper(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. */
|
/** Notifies the VcnManagementService that external dependencies can be set up. */
|
||||||
public void systemReady() {
|
public void systemReady() {
|
||||||
mContext.getSystemService(ConnectivityManager.class)
|
mContext.getSystemService(ConnectivityManager.class)
|
||||||
.registerNetworkProvider(mNetworkProvider);
|
.registerNetworkProvider(mNetworkProvider);
|
||||||
|
mTelephonySubscriptionTracker.register();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enforcePrimaryUser() {
|
private void enforcePrimaryUser() {
|
||||||
@@ -277,27 +332,112 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
|||||||
"Carrier privilege required for subscription group to set VCN Config");
|
"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.
|
* Sets a VCN config for a given subscription group.
|
||||||
*
|
*
|
||||||
* <p>Implements the IVcnManagementService Binder interface.
|
* <p>Implements the IVcnManagementService Binder interface.
|
||||||
*/
|
*/
|
||||||
@Override
|
@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(subscriptionGroup, "subscriptionGroup was null");
|
||||||
requireNonNull(config, "config 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);
|
enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
|
||||||
|
|
||||||
synchronized (mLock) {
|
Binder.withCleanCallingIdentity(() -> {
|
||||||
mConfigs.put(subscriptionGroup, config);
|
synchronized (mLock) {
|
||||||
|
mConfigs.put(subscriptionGroup, config);
|
||||||
|
startOrUpdateVcnLocked(subscriptionGroup, config);
|
||||||
|
|
||||||
// Must be done synchronously to ensure that writes do not happen out-of-order.
|
writeConfigsToDiskLocked();
|
||||||
writeConfigsToDiskLocked();
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
// TODO: Clear Binder calling identity
|
|
||||||
// TODO: Trigger startup as necessary
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -308,18 +448,21 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
|||||||
@Override
|
@Override
|
||||||
public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
|
public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
|
||||||
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
|
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
|
||||||
|
Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup);
|
||||||
|
|
||||||
enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
|
enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
|
||||||
|
|
||||||
synchronized (mLock) {
|
Binder.withCleanCallingIdentity(() -> {
|
||||||
mConfigs.remove(subscriptionGroup);
|
synchronized (mLock) {
|
||||||
|
mConfigs.remove(subscriptionGroup);
|
||||||
|
|
||||||
// Must be done synchronously to ensure that writes do not happen out-of-order.
|
if (mVcns.containsKey(subscriptionGroup)) {
|
||||||
writeConfigsToDiskLocked();
|
mVcns.remove(subscriptionGroup).teardownAsynchronously();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Clear Binder calling identity
|
writeConfigsToDiskLocked();
|
||||||
// TODO: Trigger teardown as necessary
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
@@ -345,19 +488,11 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Get current configuration list for testing purposes */
|
||||||
* Network provider for VCN networks.
|
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||||
*
|
public Map<ParcelUuid, Vcn> getAllVcns() {
|
||||||
* @hide
|
synchronized (mLock) {
|
||||||
*/
|
return Collections.unmodifiableMap(mVcns);
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import android.telephony.CarrierConfigManager;
|
|||||||
import android.telephony.SubscriptionInfo;
|
import android.telephony.SubscriptionInfo;
|
||||||
import android.telephony.SubscriptionManager;
|
import android.telephony.SubscriptionManager;
|
||||||
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
import android.util.ArrayMap;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
@@ -79,6 +81,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
|||||||
@NonNull private final TelephonySubscriptionTrackerCallback mCallback;
|
@NonNull private final TelephonySubscriptionTrackerCallback mCallback;
|
||||||
@NonNull private final Dependencies mDeps;
|
@NonNull private final Dependencies mDeps;
|
||||||
|
|
||||||
|
@NonNull private final TelephonyManager mTelephonyManager;
|
||||||
@NonNull private final SubscriptionManager mSubscriptionManager;
|
@NonNull private final SubscriptionManager mSubscriptionManager;
|
||||||
@NonNull private final CarrierConfigManager mCarrierConfigManager;
|
@NonNull private final CarrierConfigManager mCarrierConfigManager;
|
||||||
|
|
||||||
@@ -106,6 +109,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
|||||||
mCallback = Objects.requireNonNull(callback, "Missing callback");
|
mCallback = Objects.requireNonNull(callback, "Missing callback");
|
||||||
mDeps = Objects.requireNonNull(deps, "Missing deps");
|
mDeps = Objects.requireNonNull(deps, "Missing deps");
|
||||||
|
|
||||||
|
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
|
||||||
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
|
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
|
||||||
mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.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.
|
* so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking.
|
||||||
*/
|
*/
|
||||||
public void handleSubscriptionsChanged() {
|
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 Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>();
|
||||||
|
|
||||||
final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList();
|
final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList();
|
||||||
@@ -166,12 +170,22 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
|||||||
// group.
|
// group.
|
||||||
if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX
|
if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX
|
||||||
&& mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) {
|
&& 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 =
|
final TelephonySubscriptionSnapshot newSnapshot =
|
||||||
new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups);
|
new TelephonySubscriptionSnapshot(newSubIdToGroupMap, 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)) {
|
||||||
@@ -231,22 +245,40 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
|||||||
/** 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 Map<Integer, ParcelUuid> mSubIdToGroupMap;
|
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)
|
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||||
TelephonySubscriptionSnapshot(
|
TelephonySubscriptionSnapshot(
|
||||||
@NonNull Map<Integer, ParcelUuid> subIdToGroupMap,
|
@NonNull Map<Integer, ParcelUuid> subIdToGroupMap,
|
||||||
@NonNull Set<ParcelUuid> activeGroups) {
|
@NonNull Map<ParcelUuid, Set<String>> privilegedPackages) {
|
||||||
mSubIdToGroupMap = Collections.unmodifiableMap(
|
Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null");
|
||||||
Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"));
|
Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");
|
||||||
mActiveGroups = Collections.unmodifiableSet(
|
|
||||||
Objects.requireNonNull(activeGroups, "activeGroups 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 */
|
/** Returns the active subscription groups */
|
||||||
@NonNull
|
@NonNull
|
||||||
public Set<ParcelUuid> getActiveSubscriptionGroups() {
|
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. */
|
/** Returns the Subscription Group for a given subId. */
|
||||||
@@ -273,7 +305,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(mSubIdToGroupMap, mActiveGroups);
|
return Objects.hash(mSubIdToGroupMap, mPrivilegedPackages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -285,7 +317,15 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
|||||||
final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;
|
final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;
|
||||||
|
|
||||||
return mSubIdToGroupMap.equals(other.mSubIdToGroupMap)
|
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;
|
package com.android.server.vcn;
|
||||||
|
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
import android.net.NetworkRequest;
|
import android.net.NetworkRequest;
|
||||||
import android.net.vcn.VcnConfig;
|
import android.net.vcn.VcnConfig;
|
||||||
|
import android.net.vcn.VcnGatewayConnectionConfig;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.ParcelUuid;
|
import android.os.ParcelUuid;
|
||||||
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an single instance of a VCN.
|
* Represents an single instance of a VCN.
|
||||||
*
|
*
|
||||||
* <p>Each Vcn instance manages all tunnels for a given subscription group, including per-capability
|
* <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group,
|
||||||
* networks, network selection, and multi-homing.
|
* including per-capability networks, network selection, and multi-homing.
|
||||||
*
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public class Vcn extends Handler {
|
public class Vcn extends Handler {
|
||||||
private static final String TAG = Vcn.class.getSimpleName();
|
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 VcnContext mVcnContext;
|
||||||
@NonNull private final ParcelUuid mSubscriptionGroup;
|
@NonNull private final ParcelUuid mSubscriptionGroup;
|
||||||
@NonNull private final Dependencies mDeps;
|
@NonNull private final Dependencies mDeps;
|
||||||
|
@NonNull private final VcnNetworkRequestListener mRequestListener;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
|
||||||
|
new HashMap<>();
|
||||||
|
|
||||||
@NonNull private VcnConfig mConfig;
|
@NonNull private VcnConfig mConfig;
|
||||||
|
|
||||||
|
private boolean mIsRunning = true;
|
||||||
|
|
||||||
public Vcn(
|
public Vcn(
|
||||||
@NonNull VcnContext vcnContext,
|
@NonNull VcnContext vcnContext,
|
||||||
@NonNull ParcelUuid subscriptionGroup,
|
@NonNull ParcelUuid subscriptionGroup,
|
||||||
@@ -58,31 +95,123 @@ public class Vcn extends Handler {
|
|||||||
mVcnContext = vcnContext;
|
mVcnContext = vcnContext;
|
||||||
mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
|
mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
|
||||||
mDeps = Objects.requireNonNull(deps, "Missing deps");
|
mDeps = Objects.requireNonNull(deps, "Missing deps");
|
||||||
|
mRequestListener = new VcnNetworkRequestListener();
|
||||||
|
|
||||||
mConfig = Objects.requireNonNull(config, "Missing config");
|
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 */
|
/** Asynchronously updates the configuration and triggers a re-evaluation of Networks */
|
||||||
public void updateConfig(@NonNull VcnConfig config) {
|
public void updateConfig(@NonNull VcnConfig config) {
|
||||||
Objects.requireNonNull(config, "Missing 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 */
|
/** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */
|
||||||
public void teardown() {
|
public void teardownAsynchronously() {
|
||||||
// TODO: Proxy to handler, and teardown there.
|
sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Notifies this Vcn instance of a new NetworkRequest */
|
private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
|
||||||
public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
|
@Override
|
||||||
Objects.requireNonNull(request, "Missing request");
|
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
|
@Override
|
||||||
public void handleMessage(@NonNull Message msg) {
|
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 */
|
/** Retrieves the network score for a VCN Network */
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import android.annotation.NonNull;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
|
||||||
import com.android.server.VcnManagementService.VcnNetworkProvider;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTr
|
|||||||
mDeps.newUnderlyingNetworkTracker(mVcnContext, subscriptionGroup, this);
|
mDeps.newUnderlyingNetworkTracker(mVcnContext, subscriptionGroup, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tears down this GatewayConnection, and any resources used */
|
/** Asynchronously tears down this GatewayConnection, and any resources used */
|
||||||
public void teardown() {
|
public void teardownAsynchronously() {
|
||||||
mUnderlyingNetworkTracker.teardown();
|
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.assertEquals;
|
||||||
import static org.junit.Assert.fail;
|
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 android.os.Parcel;
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@@ -33,12 +38,15 @@ import java.util.Set;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public class VcnConfigTest {
|
public class VcnConfigTest {
|
||||||
|
private static final String TEST_PACKAGE_NAME = VcnConfigTest.class.getPackage().getName();
|
||||||
private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS =
|
private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS =
|
||||||
Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig());
|
Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig());
|
||||||
|
|
||||||
|
private final Context mContext = mock(Context.class);
|
||||||
|
|
||||||
// Public visibility for VcnManagementServiceTest
|
// Public visibility for VcnManagementServiceTest
|
||||||
public static VcnConfig buildTestConfig() {
|
public static VcnConfig buildTestConfig(@NonNull Context context) {
|
||||||
VcnConfig.Builder builder = new VcnConfig.Builder();
|
VcnConfig.Builder builder = new VcnConfig.Builder(context);
|
||||||
|
|
||||||
for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
|
for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
|
||||||
builder.addGatewayConnectionConfig(gatewayConnectionConfig);
|
builder.addGatewayConnectionConfig(gatewayConnectionConfig);
|
||||||
@@ -47,10 +55,24 @@ public class VcnConfigTest {
|
|||||||
return builder.build();
|
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
|
@Test
|
||||||
public void testBuilderRequiresGatewayConnectionConfig() {
|
public void testBuilderRequiresGatewayConnectionConfig() {
|
||||||
try {
|
try {
|
||||||
new VcnConfig.Builder().build();
|
new VcnConfig.Builder(mContext).build();
|
||||||
fail("Expected exception due to no VcnGatewayConnectionConfigs provided");
|
fail("Expected exception due to no VcnGatewayConnectionConfigs provided");
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
}
|
}
|
||||||
@@ -58,21 +80,22 @@ public class VcnConfigTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBuilderAndGetters() {
|
public void testBuilderAndGetters() {
|
||||||
final VcnConfig config = buildTestConfig();
|
final VcnConfig config = buildTestConfig(mContext);
|
||||||
|
|
||||||
|
assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
|
||||||
assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
|
assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPersistableBundle() {
|
public void testPersistableBundle() {
|
||||||
final VcnConfig config = buildTestConfig();
|
final VcnConfig config = buildTestConfig(mContext);
|
||||||
|
|
||||||
assertEquals(config, new VcnConfig(config.toPersistableBundle()));
|
assertEquals(config, new VcnConfig(config.toPersistableBundle()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParceling() {
|
public void testParceling() {
|
||||||
final VcnConfig config = buildTestConfig();
|
final VcnConfig config = buildTestConfig(mContext);
|
||||||
|
|
||||||
Parcel parcel = Parcel.obtain();
|
Parcel parcel = Parcel.obtain();
|
||||||
config.writeToParcel(parcel, 0);
|
config.writeToParcel(parcel, 0);
|
||||||
|
|||||||
@@ -16,16 +16,23 @@
|
|||||||
|
|
||||||
package com.android.server;
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Mockito.any;
|
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.doReturn;
|
||||||
import static org.mockito.Mockito.eq;
|
import static org.mockito.Mockito.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import android.app.AppOpsManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.vcn.VcnConfig;
|
import android.net.vcn.VcnConfig;
|
||||||
@@ -42,29 +49,47 @@ import android.telephony.TelephonyManager;
|
|||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
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 com.android.server.vcn.util.PersistableBundleUtils;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/** Tests for {@link VcnManagementService}. */
|
/** Tests for {@link VcnManagementService}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public class VcnManagementServiceTest {
|
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_1 = new ParcelUuid(new UUID(0, 0));
|
||||||
private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1));
|
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 =
|
private static final Map<ParcelUuid, VcnConfig> TEST_VCN_CONFIG_MAP =
|
||||||
Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG));
|
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 =
|
private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO =
|
||||||
new SubscriptionInfo(
|
new SubscriptionInfo(
|
||||||
1 /* id */,
|
TEST_SUBSCRIPTION_ID /* id */,
|
||||||
"" /* iccId */,
|
"" /* iccId */,
|
||||||
0 /* simSlotIndex */,
|
0 /* simSlotIndex */,
|
||||||
"Carrier" /* displayName */,
|
"Carrier" /* displayName */,
|
||||||
@@ -92,22 +117,48 @@ public class VcnManagementServiceTest {
|
|||||||
private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class);
|
private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class);
|
||||||
private final TelephonyManager mTelMgr = mock(TelephonyManager.class);
|
private final TelephonyManager mTelMgr = mock(TelephonyManager.class);
|
||||||
private final SubscriptionManager mSubMgr = mock(SubscriptionManager.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 =
|
private final PersistableBundleUtils.LockingReadWriteHelper mConfigReadWriteHelper =
|
||||||
mock(PersistableBundleUtils.LockingReadWriteHelper.class);
|
mock(PersistableBundleUtils.LockingReadWriteHelper.class);
|
||||||
|
private final TelephonySubscriptionTracker mSubscriptionTracker =
|
||||||
|
mock(TelephonySubscriptionTracker.class);
|
||||||
|
|
||||||
|
private final VcnManagementService mVcnMgmtSvc;
|
||||||
|
|
||||||
public VcnManagementServiceTest() throws Exception {
|
public VcnManagementServiceTest() throws Exception {
|
||||||
setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
|
setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
|
||||||
setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class);
|
setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class);
|
||||||
setupSystemService(
|
setupSystemService(
|
||||||
mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class);
|
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(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)
|
doReturn(mConfigReadWriteHelper)
|
||||||
.when(mMockDeps)
|
.when(mMockDeps)
|
||||||
.newPersistableBundleLockingReadWriteHelper(any());
|
.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 =
|
final PersistableBundle bundle =
|
||||||
PersistableBundleUtils.fromMap(
|
PersistableBundleUtils.fromMap(
|
||||||
TEST_VCN_CONFIG_MAP,
|
TEST_VCN_CONFIG_MAP,
|
||||||
@@ -117,6 +168,9 @@ public class VcnManagementServiceTest {
|
|||||||
|
|
||||||
setupMockedCarrierPrivilege(true);
|
setupMockedCarrierPrivilege(true);
|
||||||
mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
|
mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
|
||||||
|
|
||||||
|
// Make sure the profiles are loaded.
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSystemService(Object service, String name, Class<?> serviceClass) {
|
private void setupSystemService(Object service, String name, Class<?> serviceClass) {
|
||||||
@@ -137,8 +191,8 @@ public class VcnManagementServiceTest {
|
|||||||
public void testSystemReady() throws Exception {
|
public void testSystemReady() throws Exception {
|
||||||
mVcnMgmtSvc.systemReady();
|
mVcnMgmtSvc.systemReady();
|
||||||
|
|
||||||
verify(mConnMgr)
|
verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class));
|
||||||
.registerNetworkProvider(any(VcnManagementService.VcnNetworkProvider.class));
|
verify(mSubscriptionTracker).register();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -171,12 +225,110 @@ public class VcnManagementServiceTest {
|
|||||||
verify(mConfigReadWriteHelper).readFromDisk();
|
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
|
@Test
|
||||||
public void testSetVcnConfigRequiresNonSystemServer() throws Exception {
|
public void testSetVcnConfigRequiresNonSystemServer() throws Exception {
|
||||||
doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
|
doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
|
||||||
|
|
||||||
try {
|
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");
|
fail("Expected IllegalStateException exception for system server");
|
||||||
} catch (IllegalStateException expected) {
|
} catch (IllegalStateException expected) {
|
||||||
}
|
}
|
||||||
@@ -184,12 +336,12 @@ public class VcnManagementServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetVcnConfigRequiresSystemUser() throws Exception {
|
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)
|
.when(mMockDeps)
|
||||||
.getBinderCallingUid();
|
.getBinderCallingUid();
|
||||||
|
|
||||||
try {
|
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");
|
fail("Expected security exception for non system user");
|
||||||
} catch (SecurityException expected) {
|
} catch (SecurityException expected) {
|
||||||
}
|
}
|
||||||
@@ -200,16 +352,25 @@ public class VcnManagementServiceTest {
|
|||||||
setupMockedCarrierPrivilege(false);
|
setupMockedCarrierPrivilege(false);
|
||||||
|
|
||||||
try {
|
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");
|
fail("Expected security exception for missing carrier privileges");
|
||||||
} catch (SecurityException expected) {
|
} 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
|
@Test
|
||||||
public void testSetVcnConfig() throws Exception {
|
public void testSetVcnConfig() throws Exception {
|
||||||
// Use a different UUID to simulate a new VCN config.
|
// 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));
|
assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2));
|
||||||
verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
|
verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
|
||||||
}
|
}
|
||||||
@@ -227,7 +388,7 @@ public class VcnManagementServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClearVcnConfigRequiresSystemUser() throws Exception {
|
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)
|
.when(mMockDeps)
|
||||||
.getBinderCallingUid();
|
.getBinderCallingUid();
|
||||||
|
|
||||||
@@ -255,4 +416,26 @@ public class VcnManagementServiceTest {
|
|||||||
assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
|
assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
|
||||||
verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
|
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.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.doReturn;
|
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.verify;
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
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.annotation.NonNull;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -49,6 +54,7 @@ import android.telephony.CarrierConfigManager;
|
|||||||
import android.telephony.SubscriptionInfo;
|
import android.telephony.SubscriptionInfo;
|
||||||
import android.telephony.SubscriptionManager;
|
import android.telephony.SubscriptionManager;
|
||||||
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
@@ -63,6 +69,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -71,12 +78,16 @@ import java.util.UUID;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public class TelephonySubscriptionTrackerTest {
|
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 ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID());
|
||||||
private static final int TEST_SIM_SLOT_INDEX = 1;
|
private static final int TEST_SIM_SLOT_INDEX = 1;
|
||||||
private static final int TEST_SUBSCRIPTION_ID_1 = 2;
|
private static final int TEST_SUBSCRIPTION_ID_1 = 2;
|
||||||
private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class);
|
private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class);
|
||||||
private static final int TEST_SUBSCRIPTION_ID_2 = 3;
|
private static final int TEST_SUBSCRIPTION_ID_2 = 3;
|
||||||
private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class);
|
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;
|
private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -91,6 +102,7 @@ public class TelephonySubscriptionTrackerTest {
|
|||||||
@NonNull private final Handler mHandler;
|
@NonNull private final Handler mHandler;
|
||||||
@NonNull private final TelephonySubscriptionTracker.Dependencies mDeps;
|
@NonNull private final TelephonySubscriptionTracker.Dependencies mDeps;
|
||||||
|
|
||||||
|
@NonNull private final TelephonyManager mTelephonyManager;
|
||||||
@NonNull private final SubscriptionManager mSubscriptionManager;
|
@NonNull private final SubscriptionManager mSubscriptionManager;
|
||||||
@NonNull private final CarrierConfigManager mCarrierConfigManager;
|
@NonNull private final CarrierConfigManager mCarrierConfigManager;
|
||||||
|
|
||||||
@@ -103,9 +115,15 @@ public class TelephonySubscriptionTrackerTest {
|
|||||||
mHandler = new Handler(mTestLooper.getLooper());
|
mHandler = new Handler(mTestLooper.getLooper());
|
||||||
mDeps = mock(TelephonySubscriptionTracker.Dependencies.class);
|
mDeps = mock(TelephonySubscriptionTracker.Dependencies.class);
|
||||||
|
|
||||||
|
mTelephonyManager = mock(TelephonyManager.class);
|
||||||
mSubscriptionManager = mock(SubscriptionManager.class);
|
mSubscriptionManager = mock(SubscriptionManager.class);
|
||||||
mCarrierConfigManager = mock(CarrierConfigManager.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)
|
doReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
|
||||||
.when(mContext)
|
.when(mContext)
|
||||||
.getSystemServiceName(SubscriptionManager.class);
|
.getSystemServiceName(SubscriptionManager.class);
|
||||||
@@ -140,6 +158,9 @@ public class TelephonySubscriptionTrackerTest {
|
|||||||
doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2))
|
doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2))
|
||||||
.when(mSubscriptionManager)
|
.when(mSubscriptionManager)
|
||||||
.getAllSubscriptionInfoList();
|
.getAllSubscriptionInfoList();
|
||||||
|
|
||||||
|
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
|
||||||
|
setPrivilegedPackagesForMock(Collections.singletonList(PACKAGE_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntentFilter getIntentFilter() {
|
private IntentFilter getIntentFilter() {
|
||||||
@@ -167,13 +188,15 @@ public class TelephonySubscriptionTrackerTest {
|
|||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TelephonySubscriptionSnapshot buildExpectedSnapshot(Set<ParcelUuid> activeSubGroups) {
|
private TelephonySubscriptionSnapshot buildExpectedSnapshot(
|
||||||
return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, activeSubGroups);
|
Map<ParcelUuid, Set<String>> privilegedPackages) {
|
||||||
|
return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, privilegedPackages);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TelephonySubscriptionSnapshot buildExpectedSnapshot(
|
private TelephonySubscriptionSnapshot buildExpectedSnapshot(
|
||||||
Map<Integer, ParcelUuid> subIdToGroupMap, Set<ParcelUuid> activeSubGroups) {
|
Map<Integer, ParcelUuid> subIdToGroupMap,
|
||||||
return new TelephonySubscriptionSnapshot(subIdToGroupMap, activeSubGroups);
|
Map<ParcelUuid, Set<String>> privilegedPackages) {
|
||||||
|
return new TelephonySubscriptionSnapshot(subIdToGroupMap, privilegedPackages);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyNoActiveSubscriptions() {
|
private void verifyNoActiveSubscriptions() {
|
||||||
@@ -186,6 +209,10 @@ public class TelephonySubscriptionTrackerTest {
|
|||||||
Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1));
|
Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setPrivilegedPackagesForMock(@NonNull List<String> privilegedPackages) {
|
||||||
|
doReturn(privilegedPackages).when(mTelephonyManager).getPackagesWithCarrierPrivileges();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRegister() throws Exception {
|
public void testRegister() throws Exception {
|
||||||
verify(mContext)
|
verify(mContext)
|
||||||
@@ -223,15 +250,30 @@ public class TelephonySubscriptionTrackerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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();
|
setupReadySubIds();
|
||||||
|
|
||||||
final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
|
final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
|
||||||
listener.onSubscriptionsChanged();
|
listener.onSubscriptionsChanged();
|
||||||
mTestLooper.dispatchAll();
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
|
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
|
||||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -239,8 +281,7 @@ public class TelephonySubscriptionTrackerTest {
|
|||||||
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
||||||
mTestLooper.dispatchAll();
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
|
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
|
||||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -253,8 +294,7 @@ public class TelephonySubscriptionTrackerTest {
|
|||||||
mTestLooper.dispatchAll();
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
// Expect an empty snapshot
|
// Expect an empty snapshot
|
||||||
verify(mCallback).onNewSnapshot(
|
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap())));
|
||||||
eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -281,41 +321,57 @@ public class TelephonySubscriptionTrackerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception {
|
public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception {
|
||||||
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
|
|
||||||
|
|
||||||
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
||||||
mTestLooper.dispatchAll();
|
mTestLooper.dispatchAll();
|
||||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
|
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
|
||||||
assertNotNull(
|
assertNotNull(
|
||||||
mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
|
mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
|
||||||
|
|
||||||
doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList();
|
doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList();
|
||||||
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
||||||
mTestLooper.dispatchAll();
|
mTestLooper.dispatchAll();
|
||||||
verify(mCallback).onNewSnapshot(
|
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap())));
|
||||||
eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSlotClearedAfterValidTriggersCallbacks() throws Exception {
|
public void testSlotClearedAfterValidTriggersCallbacks() throws Exception {
|
||||||
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
|
|
||||||
|
|
||||||
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
|
||||||
mTestLooper.dispatchAll();
|
mTestLooper.dispatchAll();
|
||||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
|
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
|
||||||
assertNotNull(
|
assertNotNull(
|
||||||
mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
|
mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
|
||||||
|
|
||||||
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false));
|
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false));
|
||||||
mTestLooper.dispatchAll();
|
mTestLooper.dispatchAll();
|
||||||
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(Collections.emptySet())));
|
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap())));
|
||||||
assertNull(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
|
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
|
@Test
|
||||||
public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception {
|
public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception {
|
||||||
final TelephonySubscriptionSnapshot snapshot =
|
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_1));
|
||||||
assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2));
|
assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2));
|
||||||
@@ -324,7 +380,7 @@ public class TelephonySubscriptionTrackerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception {
|
public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception {
|
||||||
final TelephonySubscriptionSnapshot snapshot =
|
final TelephonySubscriptionSnapshot snapshot =
|
||||||
new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet());
|
new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, 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)),
|
||||||
|
|||||||
@@ -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