Loading core/java/android/net/vcn/IVcnManagementService.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -23,6 +23,6 @@ import android.os.ParcelUuid; * @hide */ interface IVcnManagementService { void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config); void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName); void clearVcnConfig(in ParcelUuid subscriptionGroup); } core/java/android/net/vcn/VcnConfig.java +37 −6 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; Loading @@ -45,11 +46,17 @@ import java.util.Set; public final class VcnConfig implements Parcelable { @NonNull private static final String TAG = VcnConfig.class.getSimpleName(); private static final String PACKAGE_NAME_KEY = "mPackageName"; @NonNull private final String mPackageName; private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs"; @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs; private VcnConfig(@NonNull Set<VcnGatewayConnectionConfig> tunnelConfigs) { mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs); private VcnConfig( @NonNull String packageName, @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) { mPackageName = packageName; mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs); validate(); } Loading @@ -61,6 +68,8 @@ public final class VcnConfig implements Parcelable { */ @VisibleForTesting(visibility = Visibility.PRIVATE) public VcnConfig(@NonNull PersistableBundle in) { mPackageName = in.getString(PACKAGE_NAME_KEY); final PersistableBundle gatewayConnectionConfigsBundle = in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY); mGatewayConnectionConfigs = Loading @@ -72,8 +81,19 @@ public final class VcnConfig implements Parcelable { } private void validate() { Objects.requireNonNull(mPackageName, "packageName was null"); Preconditions.checkCollectionNotEmpty( mGatewayConnectionConfigs, "gatewayConnectionConfigs"); mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty"); } /** * Retrieve the package name of the provisioning app. * * @hide */ @NonNull public String getProvisioningPackageName() { return mPackageName; } /** Retrieves the set of configured tunnels. */ Loading @@ -91,6 +111,8 @@ public final class VcnConfig implements Parcelable { public PersistableBundle toPersistableBundle() { final PersistableBundle result = new PersistableBundle(); result.putString(PACKAGE_NAME_KEY, mPackageName); final PersistableBundle gatewayConnectionConfigsBundle = PersistableBundleUtils.fromList( new ArrayList<>(mGatewayConnectionConfigs), Loading @@ -102,7 +124,7 @@ public final class VcnConfig implements Parcelable { @Override public int hashCode() { return Objects.hash(mGatewayConnectionConfigs); return Objects.hash(mPackageName, mGatewayConnectionConfigs); } @Override Loading @@ -112,7 +134,8 @@ public final class VcnConfig implements Parcelable { } final VcnConfig rhs = (VcnConfig) other; return mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs); return mPackageName.equals(rhs.mPackageName) && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs); } // Parcelable methods Loading Loading @@ -143,9 +166,17 @@ public final class VcnConfig implements Parcelable { /** This class is used to incrementally build {@link VcnConfig} objects. */ public static class Builder { @NonNull private final String mPackageName; @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>(); public Builder(@NonNull Context context) { Objects.requireNonNull(context, "context was null"); mPackageName = context.getOpPackageName(); } /** * Adds a configuration for an individual gateway connection. * Loading @@ -168,7 +199,7 @@ public final class VcnConfig implements Parcelable { */ @NonNull public VcnConfig build() { return new VcnConfig(mGatewayConnectionConfigs); return new VcnConfig(mPackageName, mGatewayConnectionConfigs); } } } core/java/android/net/vcn/VcnManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -101,7 +101,7 @@ public final class VcnManager { requireNonNull(config, "config was null"); try { mService.setVcnConfig(subscriptionGroup, config); mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName()); } catch (ServiceSpecificException e) { throw new IOException(e); } catch (RemoteException e) { Loading services/core/java/com/android/server/VcnManagementService.java +167 −32 Original line number Diff line number Diff line Loading @@ -16,13 +16,15 @@ package com.android.server; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkProvider; import android.net.NetworkRequest; import android.net.vcn.IVcnManagementService; import android.net.vcn.VcnConfig; import android.os.Binder; Loading @@ -43,6 +45,10 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; import com.android.server.vcn.VcnNetworkProvider; import com.android.server.vcn.util.PersistableBundleUtils; import java.io.IOException; Loading @@ -51,6 +57,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; /** * VcnManagementService manages Virtual Carrier Network profiles and lifecycles. Loading Loading @@ -115,6 +122,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { @VisibleForTesting(visibility = Visibility.PRIVATE) static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml"; // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS @VisibleForTesting(visibility = Visibility.PRIVATE) static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30); /* Binder context for this service */ @NonNull private final Context mContext; @NonNull private final Dependencies mDeps; Loading @@ -122,11 +133,23 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final Looper mLooper; @NonNull private final Handler mHandler; @NonNull private final VcnNetworkProvider mNetworkProvider; @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb; @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker; @NonNull private final VcnContext mVcnContext; @GuardedBy("mLock") @NonNull private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>(); @GuardedBy("mLock") @NonNull private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>(); @GuardedBy("mLock") @NonNull private TelephonySubscriptionSnapshot mLastSnapshot = TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT; @NonNull private final Object mLock = new Object(); @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper; Loading @@ -139,8 +162,12 @@ public class VcnManagementService extends IVcnManagementService.Stub { mLooper = mDeps.getLooper(); mHandler = new Handler(mLooper); mNetworkProvider = new VcnNetworkProvider(mContext, mLooper); mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback(); mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker( mContext, mLooper, mTelephonySubscriptionTrackerCb); mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE); mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider); // Run on handler to ensure I/O does not block system server startup mHandler.post(() -> { Loading Loading @@ -174,7 +201,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { mConfigs.put(entry.getKey(), entry.getValue()); } } // TODO: Trigger re-evaluation of active VCNs; start/stop VCNs as needed. // Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty // snapshot, and therefore safe even before telephony subscriptions are loaded. mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot); } } }); Loading Loading @@ -203,6 +233,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { return mHandlerThread.getLooper(); } /** Creates a new VcnInstance using the provided configuration */ public TelephonySubscriptionTracker newTelephonySubscriptionTracker( @NonNull Context context, @NonNull Looper looper, @NonNull TelephonySubscriptionTrackerCallback callback) { return new TelephonySubscriptionTracker(context, new Handler(looper), callback); } /** * Retrieves the caller's UID * Loading @@ -225,12 +263,29 @@ public class VcnManagementService extends IVcnManagementService.Stub { newPersistableBundleLockingReadWriteHelper(@NonNull String path) { return new PersistableBundleUtils.LockingReadWriteHelper(path); } /** Creates a new VcnContext */ public VcnContext newVcnContext( @NonNull Context context, @NonNull Looper looper, @NonNull VcnNetworkProvider vcnNetworkProvider) { return new VcnContext(context, looper, vcnNetworkProvider); } /** Creates a new Vcn instance using the provided configuration */ public Vcn newVcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { return new Vcn(vcnContext, subscriptionGroup, config); } } /** Notifies the VcnManagementService that external dependencies can be set up. */ public void systemReady() { mContext.getSystemService(ConnectivityManager.class) .registerNetworkProvider(mNetworkProvider); mTelephonySubscriptionTracker.register(); } private void enforcePrimaryUser() { Loading Loading @@ -277,27 +332,112 @@ public class VcnManagementService extends IVcnManagementService.Stub { "Carrier privilege required for subscription group to set VCN Config"); } private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback { /** * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker} * * <p>Start any unstarted VCN instances * * @hide */ public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { // Startup VCN instances synchronized (mLock) { mLastSnapshot = snapshot; // Start any VCN instances as necessary for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) { if (snapshot.packageHasPermissionsForSubscriptionGroup( entry.getKey(), entry.getValue().getProvisioningPackageName())) { if (!mVcns.containsKey(entry.getKey())) { startVcnLocked(entry.getKey(), entry.getValue()); } // Cancel any scheduled teardowns for active subscriptions mHandler.removeCallbacksAndMessages(mVcns.get(entry.getKey())); } } // Schedule teardown of any VCN instances that have lost carrier privileges (after a // delay) for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) { final VcnConfig config = mConfigs.get(entry.getKey()); if (config == null || !snapshot.packageHasPermissionsForSubscriptionGroup( entry.getKey(), config.getProvisioningPackageName())) { final ParcelUuid uuidToTeardown = entry.getKey(); final Vcn instanceToTeardown = entry.getValue(); mHandler.postDelayed(() -> { synchronized (mLock) { // Guard against case where this is run after a old instance was // torn down, and a new instance was started. Verify to ensure // correct instance is torn down. This could happen as a result of a // Carrier App manually removing/adding a VcnConfig. if (mVcns.get(uuidToTeardown) == instanceToTeardown) { mVcns.remove(uuidToTeardown).teardownAsynchronously(); } } }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); } } } } } @GuardedBy("mLock") private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup); // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active // VCN. final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config); mVcns.put(subscriptionGroup, newInstance); } @GuardedBy("mLock") private void startOrUpdateVcnLocked( @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { Slog.v(TAG, "Starting or updating VCN config for subGrp: " + subscriptionGroup); if (mVcns.containsKey(subscriptionGroup)) { mVcns.get(subscriptionGroup).updateConfig(config); } else { startVcnLocked(subscriptionGroup, config); } } /** * Sets a VCN config for a given subscription group. * * <p>Implements the IVcnManagementService Binder interface. */ @Override public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { public void setVcnConfig( @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull String opPkgName) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); requireNonNull(config, "config was null"); requireNonNull(opPkgName, "opPkgName was null"); if (!config.getProvisioningPackageName().equals(opPkgName)) { throw new IllegalArgumentException("Mismatched caller and VcnConfig creator"); } Slog.v(TAG, "VCN config updated for subGrp: " + subscriptionGroup); mContext.getSystemService(AppOpsManager.class) .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName()); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { mConfigs.put(subscriptionGroup, config); startOrUpdateVcnLocked(subscriptionGroup, config); // Must be done synchronously to ensure that writes do not happen out-of-order. writeConfigsToDiskLocked(); } // TODO: Clear Binder calling identity // TODO: Trigger startup as necessary }); } /** Loading @@ -308,18 +448,21 @@ public class VcnManagementService extends IVcnManagementService.Stub { @Override public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { mConfigs.remove(subscriptionGroup); // Must be done synchronously to ensure that writes do not happen out-of-order. writeConfigsToDiskLocked(); if (mVcns.containsKey(subscriptionGroup)) { mVcns.remove(subscriptionGroup).teardownAsynchronously(); } // TODO: Clear Binder calling identity // TODO: Trigger teardown as necessary writeConfigsToDiskLocked(); } }); } @GuardedBy("mLock") Loading @@ -345,19 +488,11 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } /** * Network provider for VCN networks. * * @hide */ public class VcnNetworkProvider extends NetworkProvider { VcnNetworkProvider(Context context, Looper looper) { super(context, looper, VcnNetworkProvider.class.getSimpleName()); } @Override public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { // TODO: Handle network requests - Ensure VCN started, and start appropriate tunnels. /** Get current configuration list for testing purposes */ @VisibleForTesting(visibility = Visibility.PRIVATE) public Map<ParcelUuid, Vcn> getAllVcns() { synchronized (mLock) { return Collections.unmodifiableMap(mVcns); } } } services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +52 −12 Original line number Diff line number Diff line Loading @@ -36,6 +36,8 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; Loading Loading @@ -79,6 +81,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @NonNull private final TelephonySubscriptionTrackerCallback mCallback; @NonNull private final Dependencies mDeps; @NonNull private final TelephonyManager mTelephonyManager; @NonNull private final SubscriptionManager mSubscriptionManager; @NonNull private final CarrierConfigManager mCarrierConfigManager; Loading Loading @@ -106,6 +109,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { mCallback = Objects.requireNonNull(callback, "Missing callback"); mDeps = Objects.requireNonNull(deps, "Missing deps"); mTelephonyManager = mContext.getSystemService(TelephonyManager.class); mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class); Loading Loading @@ -139,7 +143,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking. */ public void handleSubscriptionsChanged() { final Set<ParcelUuid> activeSubGroups = new ArraySet<>(); final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>(); final Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>(); final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList(); Loading @@ -166,12 +170,22 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { // group. if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) { activeSubGroups.add(subInfo.getGroupUuid()); // TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker final TelephonyManager subIdSpecificTelephonyManager = mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()); final ParcelUuid subGroup = subInfo.getGroupUuid(); final Set<String> pkgs = privilegedPackages.getOrDefault(subGroup, new ArraySet<>()); pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges()); privilegedPackages.put(subGroup, pkgs); } } final TelephonySubscriptionSnapshot newSnapshot = new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups); new TelephonySubscriptionSnapshot(newSubIdToGroupMap, privilegedPackages); // If snapshot was meaningfully updated, fire the callback if (!newSnapshot.equals(mCurrentSnapshot)) { Loading Loading @@ -231,22 +245,40 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */ public static class TelephonySubscriptionSnapshot { private final Map<Integer, ParcelUuid> mSubIdToGroupMap; private final Set<ParcelUuid> mActiveGroups; private final Map<ParcelUuid, Set<String>> mPrivilegedPackages; public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT = new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptyMap()); @VisibleForTesting(visibility = Visibility.PRIVATE) TelephonySubscriptionSnapshot( @NonNull Map<Integer, ParcelUuid> subIdToGroupMap, @NonNull Set<ParcelUuid> activeGroups) { mSubIdToGroupMap = Collections.unmodifiableMap( Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null")); mActiveGroups = Collections.unmodifiableSet( Objects.requireNonNull(activeGroups, "activeGroups was null")); @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) { Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"); Objects.requireNonNull(privilegedPackages, "privilegedPackages was null"); mSubIdToGroupMap = Collections.unmodifiableMap(subIdToGroupMap); final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>(); for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) { unmodifiableInnerSets.put( entry.getKey(), Collections.unmodifiableSet(entry.getValue())); } mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets); } /** Returns the active subscription groups */ @NonNull public Set<ParcelUuid> getActiveSubscriptionGroups() { return mActiveGroups; return mPrivilegedPackages.keySet(); } /** Checks if the provided package is carrier privileged for the specified sub group. */ public boolean packageHasPermissionsForSubscriptionGroup( @NonNull ParcelUuid subGrp, @NonNull String packageName) { final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp); return privilegedPackages != null && privilegedPackages.contains(packageName); } /** Returns the Subscription Group for a given subId. */ Loading @@ -273,7 +305,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @Override public int hashCode() { return Objects.hash(mSubIdToGroupMap, mActiveGroups); return Objects.hash(mSubIdToGroupMap, mPrivilegedPackages); } @Override Loading @@ -285,7 +317,15 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj; return mSubIdToGroupMap.equals(other.mSubIdToGroupMap) && mActiveGroups.equals(other.mActiveGroups); && mPrivilegedPackages.equals(other.mPrivilegedPackages); } @Override public String toString() { return "TelephonySubscriptionSnapshot{ " + "mSubIdToGroupMap=" + mSubIdToGroupMap + ", mPrivilegedPackages=" + mPrivilegedPackages + " }"; } } Loading Loading
core/java/android/net/vcn/IVcnManagementService.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -23,6 +23,6 @@ import android.os.ParcelUuid; * @hide */ interface IVcnManagementService { void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config); void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName); void clearVcnConfig(in ParcelUuid subscriptionGroup); }
core/java/android/net/vcn/VcnConfig.java +37 −6 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; Loading @@ -45,11 +46,17 @@ import java.util.Set; public final class VcnConfig implements Parcelable { @NonNull private static final String TAG = VcnConfig.class.getSimpleName(); private static final String PACKAGE_NAME_KEY = "mPackageName"; @NonNull private final String mPackageName; private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs"; @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs; private VcnConfig(@NonNull Set<VcnGatewayConnectionConfig> tunnelConfigs) { mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs); private VcnConfig( @NonNull String packageName, @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) { mPackageName = packageName; mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs); validate(); } Loading @@ -61,6 +68,8 @@ public final class VcnConfig implements Parcelable { */ @VisibleForTesting(visibility = Visibility.PRIVATE) public VcnConfig(@NonNull PersistableBundle in) { mPackageName = in.getString(PACKAGE_NAME_KEY); final PersistableBundle gatewayConnectionConfigsBundle = in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY); mGatewayConnectionConfigs = Loading @@ -72,8 +81,19 @@ public final class VcnConfig implements Parcelable { } private void validate() { Objects.requireNonNull(mPackageName, "packageName was null"); Preconditions.checkCollectionNotEmpty( mGatewayConnectionConfigs, "gatewayConnectionConfigs"); mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty"); } /** * Retrieve the package name of the provisioning app. * * @hide */ @NonNull public String getProvisioningPackageName() { return mPackageName; } /** Retrieves the set of configured tunnels. */ Loading @@ -91,6 +111,8 @@ public final class VcnConfig implements Parcelable { public PersistableBundle toPersistableBundle() { final PersistableBundle result = new PersistableBundle(); result.putString(PACKAGE_NAME_KEY, mPackageName); final PersistableBundle gatewayConnectionConfigsBundle = PersistableBundleUtils.fromList( new ArrayList<>(mGatewayConnectionConfigs), Loading @@ -102,7 +124,7 @@ public final class VcnConfig implements Parcelable { @Override public int hashCode() { return Objects.hash(mGatewayConnectionConfigs); return Objects.hash(mPackageName, mGatewayConnectionConfigs); } @Override Loading @@ -112,7 +134,8 @@ public final class VcnConfig implements Parcelable { } final VcnConfig rhs = (VcnConfig) other; return mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs); return mPackageName.equals(rhs.mPackageName) && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs); } // Parcelable methods Loading Loading @@ -143,9 +166,17 @@ public final class VcnConfig implements Parcelable { /** This class is used to incrementally build {@link VcnConfig} objects. */ public static class Builder { @NonNull private final String mPackageName; @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>(); public Builder(@NonNull Context context) { Objects.requireNonNull(context, "context was null"); mPackageName = context.getOpPackageName(); } /** * Adds a configuration for an individual gateway connection. * Loading @@ -168,7 +199,7 @@ public final class VcnConfig implements Parcelable { */ @NonNull public VcnConfig build() { return new VcnConfig(mGatewayConnectionConfigs); return new VcnConfig(mPackageName, mGatewayConnectionConfigs); } } }
core/java/android/net/vcn/VcnManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -101,7 +101,7 @@ public final class VcnManager { requireNonNull(config, "config was null"); try { mService.setVcnConfig(subscriptionGroup, config); mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName()); } catch (ServiceSpecificException e) { throw new IOException(e); } catch (RemoteException e) { Loading
services/core/java/com/android/server/VcnManagementService.java +167 −32 Original line number Diff line number Diff line Loading @@ -16,13 +16,15 @@ package com.android.server; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkProvider; import android.net.NetworkRequest; import android.net.vcn.IVcnManagementService; import android.net.vcn.VcnConfig; import android.os.Binder; Loading @@ -43,6 +45,10 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; import com.android.server.vcn.VcnNetworkProvider; import com.android.server.vcn.util.PersistableBundleUtils; import java.io.IOException; Loading @@ -51,6 +57,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; /** * VcnManagementService manages Virtual Carrier Network profiles and lifecycles. Loading Loading @@ -115,6 +122,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { @VisibleForTesting(visibility = Visibility.PRIVATE) static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml"; // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS @VisibleForTesting(visibility = Visibility.PRIVATE) static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30); /* Binder context for this service */ @NonNull private final Context mContext; @NonNull private final Dependencies mDeps; Loading @@ -122,11 +133,23 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final Looper mLooper; @NonNull private final Handler mHandler; @NonNull private final VcnNetworkProvider mNetworkProvider; @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb; @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker; @NonNull private final VcnContext mVcnContext; @GuardedBy("mLock") @NonNull private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>(); @GuardedBy("mLock") @NonNull private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>(); @GuardedBy("mLock") @NonNull private TelephonySubscriptionSnapshot mLastSnapshot = TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT; @NonNull private final Object mLock = new Object(); @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper; Loading @@ -139,8 +162,12 @@ public class VcnManagementService extends IVcnManagementService.Stub { mLooper = mDeps.getLooper(); mHandler = new Handler(mLooper); mNetworkProvider = new VcnNetworkProvider(mContext, mLooper); mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback(); mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker( mContext, mLooper, mTelephonySubscriptionTrackerCb); mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE); mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider); // Run on handler to ensure I/O does not block system server startup mHandler.post(() -> { Loading Loading @@ -174,7 +201,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { mConfigs.put(entry.getKey(), entry.getValue()); } } // TODO: Trigger re-evaluation of active VCNs; start/stop VCNs as needed. // Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty // snapshot, and therefore safe even before telephony subscriptions are loaded. mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot); } } }); Loading Loading @@ -203,6 +233,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { return mHandlerThread.getLooper(); } /** Creates a new VcnInstance using the provided configuration */ public TelephonySubscriptionTracker newTelephonySubscriptionTracker( @NonNull Context context, @NonNull Looper looper, @NonNull TelephonySubscriptionTrackerCallback callback) { return new TelephonySubscriptionTracker(context, new Handler(looper), callback); } /** * Retrieves the caller's UID * Loading @@ -225,12 +263,29 @@ public class VcnManagementService extends IVcnManagementService.Stub { newPersistableBundleLockingReadWriteHelper(@NonNull String path) { return new PersistableBundleUtils.LockingReadWriteHelper(path); } /** Creates a new VcnContext */ public VcnContext newVcnContext( @NonNull Context context, @NonNull Looper looper, @NonNull VcnNetworkProvider vcnNetworkProvider) { return new VcnContext(context, looper, vcnNetworkProvider); } /** Creates a new Vcn instance using the provided configuration */ public Vcn newVcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { return new Vcn(vcnContext, subscriptionGroup, config); } } /** Notifies the VcnManagementService that external dependencies can be set up. */ public void systemReady() { mContext.getSystemService(ConnectivityManager.class) .registerNetworkProvider(mNetworkProvider); mTelephonySubscriptionTracker.register(); } private void enforcePrimaryUser() { Loading Loading @@ -277,27 +332,112 @@ public class VcnManagementService extends IVcnManagementService.Stub { "Carrier privilege required for subscription group to set VCN Config"); } private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback { /** * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker} * * <p>Start any unstarted VCN instances * * @hide */ public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { // Startup VCN instances synchronized (mLock) { mLastSnapshot = snapshot; // Start any VCN instances as necessary for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) { if (snapshot.packageHasPermissionsForSubscriptionGroup( entry.getKey(), entry.getValue().getProvisioningPackageName())) { if (!mVcns.containsKey(entry.getKey())) { startVcnLocked(entry.getKey(), entry.getValue()); } // Cancel any scheduled teardowns for active subscriptions mHandler.removeCallbacksAndMessages(mVcns.get(entry.getKey())); } } // Schedule teardown of any VCN instances that have lost carrier privileges (after a // delay) for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) { final VcnConfig config = mConfigs.get(entry.getKey()); if (config == null || !snapshot.packageHasPermissionsForSubscriptionGroup( entry.getKey(), config.getProvisioningPackageName())) { final ParcelUuid uuidToTeardown = entry.getKey(); final Vcn instanceToTeardown = entry.getValue(); mHandler.postDelayed(() -> { synchronized (mLock) { // Guard against case where this is run after a old instance was // torn down, and a new instance was started. Verify to ensure // correct instance is torn down. This could happen as a result of a // Carrier App manually removing/adding a VcnConfig. if (mVcns.get(uuidToTeardown) == instanceToTeardown) { mVcns.remove(uuidToTeardown).teardownAsynchronously(); } } }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); } } } } } @GuardedBy("mLock") private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup); // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active // VCN. final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config); mVcns.put(subscriptionGroup, newInstance); } @GuardedBy("mLock") private void startOrUpdateVcnLocked( @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { Slog.v(TAG, "Starting or updating VCN config for subGrp: " + subscriptionGroup); if (mVcns.containsKey(subscriptionGroup)) { mVcns.get(subscriptionGroup).updateConfig(config); } else { startVcnLocked(subscriptionGroup, config); } } /** * Sets a VCN config for a given subscription group. * * <p>Implements the IVcnManagementService Binder interface. */ @Override public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { public void setVcnConfig( @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull String opPkgName) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); requireNonNull(config, "config was null"); requireNonNull(opPkgName, "opPkgName was null"); if (!config.getProvisioningPackageName().equals(opPkgName)) { throw new IllegalArgumentException("Mismatched caller and VcnConfig creator"); } Slog.v(TAG, "VCN config updated for subGrp: " + subscriptionGroup); mContext.getSystemService(AppOpsManager.class) .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName()); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { mConfigs.put(subscriptionGroup, config); startOrUpdateVcnLocked(subscriptionGroup, config); // Must be done synchronously to ensure that writes do not happen out-of-order. writeConfigsToDiskLocked(); } // TODO: Clear Binder calling identity // TODO: Trigger startup as necessary }); } /** Loading @@ -308,18 +448,21 @@ public class VcnManagementService extends IVcnManagementService.Stub { @Override public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { mConfigs.remove(subscriptionGroup); // Must be done synchronously to ensure that writes do not happen out-of-order. writeConfigsToDiskLocked(); if (mVcns.containsKey(subscriptionGroup)) { mVcns.remove(subscriptionGroup).teardownAsynchronously(); } // TODO: Clear Binder calling identity // TODO: Trigger teardown as necessary writeConfigsToDiskLocked(); } }); } @GuardedBy("mLock") Loading @@ -345,19 +488,11 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } /** * Network provider for VCN networks. * * @hide */ public class VcnNetworkProvider extends NetworkProvider { VcnNetworkProvider(Context context, Looper looper) { super(context, looper, VcnNetworkProvider.class.getSimpleName()); } @Override public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { // TODO: Handle network requests - Ensure VCN started, and start appropriate tunnels. /** Get current configuration list for testing purposes */ @VisibleForTesting(visibility = Visibility.PRIVATE) public Map<ParcelUuid, Vcn> getAllVcns() { synchronized (mLock) { return Collections.unmodifiableMap(mVcns); } } }
services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +52 −12 Original line number Diff line number Diff line Loading @@ -36,6 +36,8 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; Loading Loading @@ -79,6 +81,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @NonNull private final TelephonySubscriptionTrackerCallback mCallback; @NonNull private final Dependencies mDeps; @NonNull private final TelephonyManager mTelephonyManager; @NonNull private final SubscriptionManager mSubscriptionManager; @NonNull private final CarrierConfigManager mCarrierConfigManager; Loading Loading @@ -106,6 +109,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { mCallback = Objects.requireNonNull(callback, "Missing callback"); mDeps = Objects.requireNonNull(deps, "Missing deps"); mTelephonyManager = mContext.getSystemService(TelephonyManager.class); mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class); Loading Loading @@ -139,7 +143,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking. */ public void handleSubscriptionsChanged() { final Set<ParcelUuid> activeSubGroups = new ArraySet<>(); final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>(); final Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>(); final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList(); Loading @@ -166,12 +170,22 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { // group. if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) { activeSubGroups.add(subInfo.getGroupUuid()); // TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker final TelephonyManager subIdSpecificTelephonyManager = mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()); final ParcelUuid subGroup = subInfo.getGroupUuid(); final Set<String> pkgs = privilegedPackages.getOrDefault(subGroup, new ArraySet<>()); pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges()); privilegedPackages.put(subGroup, pkgs); } } final TelephonySubscriptionSnapshot newSnapshot = new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups); new TelephonySubscriptionSnapshot(newSubIdToGroupMap, privilegedPackages); // If snapshot was meaningfully updated, fire the callback if (!newSnapshot.equals(mCurrentSnapshot)) { Loading Loading @@ -231,22 +245,40 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */ public static class TelephonySubscriptionSnapshot { private final Map<Integer, ParcelUuid> mSubIdToGroupMap; private final Set<ParcelUuid> mActiveGroups; private final Map<ParcelUuid, Set<String>> mPrivilegedPackages; public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT = new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptyMap()); @VisibleForTesting(visibility = Visibility.PRIVATE) TelephonySubscriptionSnapshot( @NonNull Map<Integer, ParcelUuid> subIdToGroupMap, @NonNull Set<ParcelUuid> activeGroups) { mSubIdToGroupMap = Collections.unmodifiableMap( Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null")); mActiveGroups = Collections.unmodifiableSet( Objects.requireNonNull(activeGroups, "activeGroups was null")); @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) { Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"); Objects.requireNonNull(privilegedPackages, "privilegedPackages was null"); mSubIdToGroupMap = Collections.unmodifiableMap(subIdToGroupMap); final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>(); for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) { unmodifiableInnerSets.put( entry.getKey(), Collections.unmodifiableSet(entry.getValue())); } mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets); } /** Returns the active subscription groups */ @NonNull public Set<ParcelUuid> getActiveSubscriptionGroups() { return mActiveGroups; return mPrivilegedPackages.keySet(); } /** Checks if the provided package is carrier privileged for the specified sub group. */ public boolean packageHasPermissionsForSubscriptionGroup( @NonNull ParcelUuid subGrp, @NonNull String packageName) { final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp); return privilegedPackages != null && privilegedPackages.contains(packageName); } /** Returns the Subscription Group for a given subId. */ Loading @@ -273,7 +305,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @Override public int hashCode() { return Objects.hash(mSubIdToGroupMap, mActiveGroups); return Objects.hash(mSubIdToGroupMap, mPrivilegedPackages); } @Override Loading @@ -285,7 +317,15 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj; return mSubIdToGroupMap.equals(other.mSubIdToGroupMap) && mActiveGroups.equals(other.mActiveGroups); && mPrivilegedPackages.equals(other.mPrivilegedPackages); } @Override public String toString() { return "TelephonySubscriptionSnapshot{ " + "mSubIdToGroupMap=" + mSubIdToGroupMap + ", mPrivilegedPackages=" + mPrivilegedPackages + " }"; } } Loading