1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.net.vcn; 17 18 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 19 import static android.net.NetworkCapabilities.TRANSPORT_TEST; 20 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 21 22 import static com.android.internal.annotations.VisibleForTesting.Visibility; 23 import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER; 24 import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER; 25 26 import android.annotation.IntDef; 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.content.Context; 30 import android.net.NetworkCapabilities; 31 import android.net.NetworkRequest; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.os.PersistableBundle; 35 import android.util.ArraySet; 36 import android.util.Log; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.internal.util.Preconditions; 40 import com.android.server.vcn.util.PersistableBundleUtils; 41 42 import java.lang.annotation.ElementType; 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.lang.annotation.Target; 46 import java.util.ArrayList; 47 import java.util.Collections; 48 import java.util.Iterator; 49 import java.util.Objects; 50 import java.util.Set; 51 52 /** 53 * This class represents a configuration for a Virtual Carrier Network. 54 * 55 * <p>Each {@link VcnGatewayConnectionConfig} instance added represents a connection that will be 56 * brought up on demand based on active {@link NetworkRequest}(s). 57 * 58 * @see VcnManager for more information on the Virtual Carrier Network feature 59 */ 60 public final class VcnConfig implements Parcelable { 61 @NonNull private static final String TAG = VcnConfig.class.getSimpleName(); 62 63 /** @hide */ 64 @Retention(RetentionPolicy.SOURCE) 65 @IntDef( 66 prefix = {"TRANSPORT_"}, 67 value = { 68 NetworkCapabilities.TRANSPORT_CELLULAR, 69 NetworkCapabilities.TRANSPORT_WIFI, 70 }) 71 @Target({ElementType.TYPE_USE}) 72 public @interface VcnUnderlyingNetworkTransport {} 73 74 private static final Set<Integer> ALLOWED_TRANSPORTS = new ArraySet<>(); 75 76 static { 77 ALLOWED_TRANSPORTS.add(TRANSPORT_WIFI); 78 ALLOWED_TRANSPORTS.add(TRANSPORT_CELLULAR); 79 ALLOWED_TRANSPORTS.add(TRANSPORT_TEST); 80 } 81 82 private static final String PACKAGE_NAME_KEY = "mPackageName"; 83 @NonNull private final String mPackageName; 84 85 private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs"; 86 @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs; 87 88 private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT = 89 Collections.singleton(TRANSPORT_WIFI); 90 private static final String RESTRICTED_TRANSPORTS_KEY = "mRestrictedTransports"; 91 @NonNull private final Set<Integer> mRestrictedTransports; 92 93 private static final String IS_TEST_MODE_PROFILE_KEY = "mIsTestModeProfile"; 94 private final boolean mIsTestModeProfile; 95 VcnConfig( @onNull String packageName, @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs, @NonNull Set<Integer> restrictedTransports, boolean isTestModeProfile)96 private VcnConfig( 97 @NonNull String packageName, 98 @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs, 99 @NonNull Set<Integer> restrictedTransports, 100 boolean isTestModeProfile) { 101 mPackageName = packageName; 102 mGatewayConnectionConfigs = 103 Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs)); 104 mRestrictedTransports = Collections.unmodifiableSet(new ArraySet<>(restrictedTransports)); 105 mIsTestModeProfile = isTestModeProfile; 106 107 validate(); 108 } 109 110 /** 111 * Deserializes a VcnConfig from a PersistableBundle. 112 * 113 * @hide 114 */ 115 @VisibleForTesting(visibility = Visibility.PRIVATE) VcnConfig(@onNull PersistableBundle in)116 public VcnConfig(@NonNull PersistableBundle in) { 117 mPackageName = in.getString(PACKAGE_NAME_KEY); 118 119 final PersistableBundle gatewayConnectionConfigsBundle = 120 in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY); 121 mGatewayConnectionConfigs = 122 new ArraySet<>( 123 PersistableBundleUtils.toList( 124 gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new)); 125 126 final PersistableBundle restrictedTransportsBundle = 127 in.getPersistableBundle(RESTRICTED_TRANSPORTS_KEY); 128 if (restrictedTransportsBundle == null) { 129 // RESTRICTED_TRANSPORTS_KEY was added in U and does not exist in VcnConfigs created in 130 // older platforms 131 mRestrictedTransports = RESTRICTED_TRANSPORTS_DEFAULT; 132 } else { 133 mRestrictedTransports = 134 new ArraySet<Integer>( 135 PersistableBundleUtils.toList( 136 restrictedTransportsBundle, INTEGER_DESERIALIZER)); 137 } 138 139 mIsTestModeProfile = in.getBoolean(IS_TEST_MODE_PROFILE_KEY); 140 141 validate(); 142 } 143 validate()144 private void validate() { 145 Objects.requireNonNull(mPackageName, "packageName was null"); 146 Preconditions.checkCollectionNotEmpty( 147 mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty"); 148 149 final Iterator<Integer> iterator = mRestrictedTransports.iterator(); 150 while (iterator.hasNext()) { 151 final int transport = iterator.next(); 152 if (!ALLOWED_TRANSPORTS.contains(transport)) { 153 iterator.remove(); 154 Log.w( 155 TAG, 156 "Found invalid transport " 157 + transport 158 + " which might be from a new version of VcnConfig"); 159 } 160 161 if (transport == TRANSPORT_TEST && !mIsTestModeProfile) { 162 throw new IllegalArgumentException( 163 "Found TRANSPORT_TEST in a non-test-mode profile"); 164 } 165 } 166 } 167 168 /** 169 * Retrieve the package name of the provisioning app. 170 * 171 * @hide 172 */ 173 @NonNull getProvisioningPackageName()174 public String getProvisioningPackageName() { 175 return mPackageName; 176 } 177 178 /** Retrieves the set of configured GatewayConnection(s). */ 179 @NonNull getGatewayConnectionConfigs()180 public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() { 181 return Collections.unmodifiableSet(mGatewayConnectionConfigs); 182 } 183 184 /** 185 * Retrieve the transports that will be restricted by the VCN. 186 * 187 * @see Builder#setRestrictedUnderlyingNetworkTransports(Set) 188 */ 189 @NonNull getRestrictedUnderlyingNetworkTransports()190 public Set<@VcnUnderlyingNetworkTransport Integer> getRestrictedUnderlyingNetworkTransports() { 191 return Collections.unmodifiableSet(mRestrictedTransports); 192 } 193 194 /** 195 * Returns whether or not this VcnConfig is restricted to test networks. 196 * 197 * @hide 198 */ isTestModeProfile()199 public boolean isTestModeProfile() { 200 return mIsTestModeProfile; 201 } 202 203 /** 204 * Serializes this object to a PersistableBundle. 205 * 206 * @hide 207 */ 208 @NonNull toPersistableBundle()209 public PersistableBundle toPersistableBundle() { 210 final PersistableBundle result = new PersistableBundle(); 211 212 result.putString(PACKAGE_NAME_KEY, mPackageName); 213 214 final PersistableBundle gatewayConnectionConfigsBundle = 215 PersistableBundleUtils.fromList( 216 new ArrayList<>(mGatewayConnectionConfigs), 217 VcnGatewayConnectionConfig::toPersistableBundle); 218 result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle); 219 220 final PersistableBundle restrictedTransportsBundle = 221 PersistableBundleUtils.fromList( 222 new ArrayList<>(mRestrictedTransports), INTEGER_SERIALIZER); 223 result.putPersistableBundle(RESTRICTED_TRANSPORTS_KEY, restrictedTransportsBundle); 224 225 result.putBoolean(IS_TEST_MODE_PROFILE_KEY, mIsTestModeProfile); 226 227 return result; 228 } 229 230 @Override hashCode()231 public int hashCode() { 232 return Objects.hash( 233 mPackageName, mGatewayConnectionConfigs, mRestrictedTransports, mIsTestModeProfile); 234 } 235 236 @Override equals(@ullable Object other)237 public boolean equals(@Nullable Object other) { 238 if (!(other instanceof VcnConfig)) { 239 return false; 240 } 241 242 final VcnConfig rhs = (VcnConfig) other; 243 return mPackageName.equals(rhs.mPackageName) 244 && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs) 245 && mRestrictedTransports.equals(rhs.mRestrictedTransports) 246 && mIsTestModeProfile == rhs.mIsTestModeProfile; 247 } 248 249 // Parcelable methods 250 251 @Override describeContents()252 public int describeContents() { 253 return 0; 254 } 255 256 @Override writeToParcel(@onNull Parcel out, int flags)257 public void writeToParcel(@NonNull Parcel out, int flags) { 258 out.writeParcelable(toPersistableBundle(), flags); 259 } 260 261 @NonNull 262 public static final Parcelable.Creator<VcnConfig> CREATOR = 263 new Parcelable.Creator<VcnConfig>() { 264 @NonNull 265 public VcnConfig createFromParcel(Parcel in) { 266 return new VcnConfig((PersistableBundle) in.readParcelable(null, android.os.PersistableBundle.class)); 267 } 268 269 @NonNull 270 public VcnConfig[] newArray(int size) { 271 return new VcnConfig[size]; 272 } 273 }; 274 275 /** This class is used to incrementally build {@link VcnConfig} objects. */ 276 public static final class Builder { 277 @NonNull private final String mPackageName; 278 279 @NonNull 280 private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>(); 281 282 @NonNull private final Set<Integer> mRestrictedTransports = new ArraySet<>(); 283 284 private boolean mIsTestModeProfile = false; 285 Builder(@onNull Context context)286 public Builder(@NonNull Context context) { 287 Objects.requireNonNull(context, "context was null"); 288 289 mPackageName = context.getOpPackageName(); 290 mRestrictedTransports.addAll(RESTRICTED_TRANSPORTS_DEFAULT); 291 } 292 293 /** 294 * Adds a configuration for an individual gateway connection. 295 * 296 * @param gatewayConnectionConfig the configuration for an individual gateway connection 297 * @return this {@link Builder} instance, for chaining 298 * @throws IllegalArgumentException if a VcnGatewayConnectionConfig has already been set for 299 * this {@link VcnConfig} with the same GatewayConnection name (as returned via {@link 300 * VcnGatewayConnectionConfig#getGatewayConnectionName()}). 301 */ 302 @NonNull addGatewayConnectionConfig( @onNull VcnGatewayConnectionConfig gatewayConnectionConfig)303 public Builder addGatewayConnectionConfig( 304 @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) { 305 Objects.requireNonNull(gatewayConnectionConfig, "gatewayConnectionConfig was null"); 306 307 for (final VcnGatewayConnectionConfig vcnGatewayConnectionConfig : 308 mGatewayConnectionConfigs) { 309 if (vcnGatewayConnectionConfig 310 .getGatewayConnectionName() 311 .equals(gatewayConnectionConfig.getGatewayConnectionName())) { 312 throw new IllegalArgumentException( 313 "GatewayConnection for specified name already exists"); 314 } 315 } 316 317 mGatewayConnectionConfigs.add(gatewayConnectionConfig); 318 return this; 319 } 320 validateRestrictedTransportsOrThrow(Set<Integer> restrictedTransports)321 private void validateRestrictedTransportsOrThrow(Set<Integer> restrictedTransports) { 322 Objects.requireNonNull(restrictedTransports, "transports was null"); 323 324 for (int transport : restrictedTransports) { 325 if (!ALLOWED_TRANSPORTS.contains(transport)) { 326 throw new IllegalArgumentException("Invalid transport " + transport); 327 } 328 } 329 } 330 331 /** 332 * Sets transports that will be restricted by the VCN. 333 * 334 * <p>In general, apps will not be able to bind to, or use a restricted network. In other 335 * words, unless the network type is marked restricted, any app can opt to use underlying 336 * networks, instead of through the VCN. 337 * 338 * @param transports transports that will be restricted by VCN. Networks that include any of 339 * the transports will be marked as restricted. {@link 340 * NetworkCapabilities#TRANSPORT_WIFI} is marked restricted by default. 341 * @return this {@link Builder} instance, for chaining 342 * @throws IllegalArgumentException if the input contains unsupported transport types. 343 * @see NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED 344 */ 345 @NonNull setRestrictedUnderlyingNetworkTransports( @onNull Set<@VcnUnderlyingNetworkTransport Integer> transports)346 public Builder setRestrictedUnderlyingNetworkTransports( 347 @NonNull Set<@VcnUnderlyingNetworkTransport Integer> transports) { 348 validateRestrictedTransportsOrThrow(transports); 349 350 mRestrictedTransports.clear(); 351 mRestrictedTransports.addAll(transports); 352 return this; 353 } 354 355 /** 356 * Restricts this VcnConfig to matching with test networks (only). 357 * 358 * <p>This method is for testing only, and must not be used by apps. Calling {@link 359 * VcnManager#setVcnConfig(ParcelUuid, VcnConfig)} with a VcnConfig where test-network usage 360 * is enabled will require the MANAGE_TEST_NETWORKS permission. 361 * 362 * @return this {@link Builder} instance, for chaining 363 * @hide 364 */ 365 @NonNull setIsTestModeProfile()366 public Builder setIsTestModeProfile() { 367 mIsTestModeProfile = true; 368 return this; 369 } 370 371 /** 372 * Builds and validates the VcnConfig. 373 * 374 * @return an immutable VcnConfig instance 375 */ 376 @NonNull build()377 public VcnConfig build() { 378 return new VcnConfig( 379 mPackageName, 380 mGatewayConnectionConfigs, 381 mRestrictedTransports, 382 mIsTestModeProfile); 383 } 384 } 385 } 386