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 java.util.Objects.requireNonNull; 19 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SystemApi; 25 import android.annotation.SystemService; 26 import android.content.Context; 27 import android.net.LinkProperties; 28 import android.net.NetworkCapabilities; 29 import android.os.Binder; 30 import android.os.ParcelUuid; 31 import android.os.RemoteException; 32 import android.os.ServiceSpecificException; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.annotations.VisibleForTesting.Visibility; 36 37 import java.io.IOException; 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.concurrent.ConcurrentHashMap; 44 import java.util.concurrent.Executor; 45 46 /** 47 * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks. 48 * 49 * <p>A VCN creates a virtualization layer to allow carriers to aggregate heterogeneous physical 50 * networks, unifying them as a single carrier network. This enables infrastructure flexibility on 51 * the part of carriers without impacting user connectivity, abstracting the physical network 52 * technologies as an implementation detail of their public network. 53 * 54 * <p>Each VCN virtualizes a carrier's network by building tunnels to a carrier's core network over 55 * carrier-managed physical links and supports a IP mobility layer to ensure seamless transitions 56 * between the underlying networks. Each VCN is configured based on a Subscription Group (see {@link 57 * android.telephony.SubscriptionManager}) and aggregates all networks that are brought up based on 58 * a profile or suggestion in the specified Subscription Group. 59 * 60 * <p>The VCN can be configured to expose one or more {@link android.net.Network}(s), each with 61 * different capabilities, allowing for APN virtualization. 62 * 63 * <p>If a tunnel fails to connect, or otherwise encounters a fatal error, the VCN will attempt to 64 * reestablish the connection. If the tunnel still has not reconnected after a system-determined 65 * timeout, the VCN Safe Mode (see below) will be entered. 66 * 67 * <p>The VCN Safe Mode ensures that users (and carriers) have a fallback to restore system 68 * connectivity to update profiles, diagnose issues, contact support, or perform other remediation 69 * tasks. In Safe Mode, the system will allow underlying cellular networks to be used as default. 70 * Additionally, during Safe Mode, the VCN will continue to retry the connections, and will 71 * automatically exit Safe Mode if all active tunnels connect successfully. 72 */ 73 @SystemService(Context.VCN_MANAGEMENT_SERVICE) 74 public class VcnManager { 75 @NonNull private static final String TAG = VcnManager.class.getSimpleName(); 76 77 /** 78 * Key for WiFi entry RSSI thresholds 79 * 80 * <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater 81 * than, or equal to this threshold. 82 * 83 * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup. 84 * 85 * @hide 86 */ 87 @NonNull 88 public static final String VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY = 89 "vcn_network_selection_wifi_entry_rssi_threshold"; 90 91 /** 92 * Key for WiFi entry RSSI thresholds 93 * 94 * <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold, 95 * the VCN will attempt to migrate away from the Carrier WiFi network. 96 * 97 * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup. 98 * 99 * @hide 100 */ 101 @NonNull 102 public static final String VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY = 103 "vcn_network_selection_wifi_exit_rssi_threshold"; 104 105 // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz 106 107 /** List of Carrier Config options to extract from Carrier Config bundles. @hide */ 108 @NonNull 109 public static final String[] VCN_RELATED_CARRIER_CONFIG_KEYS = 110 new String[] { 111 VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, 112 VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY 113 }; 114 115 private static final Map< 116 VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder> 117 REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>(); 118 119 @NonNull private final Context mContext; 120 @NonNull private final IVcnManagementService mService; 121 122 /** 123 * Construct an instance of VcnManager within an application context. 124 * 125 * @param ctx the application context for this manager 126 * @param service the VcnManagementService binder backing this manager 127 * 128 * @hide 129 */ VcnManager(@onNull Context ctx, @NonNull IVcnManagementService service)130 public VcnManager(@NonNull Context ctx, @NonNull IVcnManagementService service) { 131 mContext = requireNonNull(ctx, "missing context"); 132 mService = requireNonNull(service, "missing service"); 133 } 134 135 /** 136 * Get all currently registered VcnNetworkPolicyChangeListeners for testing purposes. 137 * 138 * @hide 139 */ 140 @VisibleForTesting(visibility = Visibility.PRIVATE) 141 @NonNull 142 public static Map<VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder> getAllPolicyListeners()143 getAllPolicyListeners() { 144 return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS); 145 } 146 147 /** 148 * Sets the VCN configuration for a given subscription group. 149 * 150 * <p>An app that has carrier privileges for any of the subscriptions in the given group may set 151 * a VCN configuration. If a configuration already exists for the given subscription group, it 152 * will be overridden. Any active VCN(s) may be forced to restart to use the new configuration. 153 * 154 * <p>This API is ONLY permitted for callers running as the primary user. 155 * 156 * @param subscriptionGroup the subscription group that the configuration should be applied to 157 * @param config the configuration parameters for the VCN 158 * @throws SecurityException if the caller does not have carrier privileges for the provided 159 * subscriptionGroup, or is not running as the primary user 160 * @throws IOException if the configuration failed to be saved and persisted to disk. This may 161 * occur due to temporary disk errors, or more permanent conditions such as a full disk. 162 */ 163 @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant setVcnConfig(@onNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config)164 public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) 165 throws IOException { 166 requireNonNull(subscriptionGroup, "subscriptionGroup was null"); 167 requireNonNull(config, "config was null"); 168 169 try { 170 mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName()); 171 } catch (ServiceSpecificException e) { 172 throw new IOException(e); 173 } catch (RemoteException e) { 174 throw e.rethrowFromSystemServer(); 175 } 176 } 177 178 /** 179 * Clears the VCN configuration for a given subscription group. 180 * 181 * <p>An app that has carrier privileges for any of the subscriptions in the given group may 182 * clear a VCN configuration. This API is ONLY permitted for callers running as the primary 183 * user. Any active VCN associated with this configuration will be torn down. 184 * 185 * @param subscriptionGroup the subscription group that the configuration should be applied to 186 * @throws SecurityException if the caller does not have carrier privileges, is not the owner of 187 * the associated configuration, or is not running as the primary user 188 * @throws IOException if the configuration failed to be cleared from disk. This may occur due 189 * to temporary disk errors, or more permanent conditions such as a full disk. 190 */ 191 @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant clearVcnConfig(@onNull ParcelUuid subscriptionGroup)192 public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) throws IOException { 193 requireNonNull(subscriptionGroup, "subscriptionGroup was null"); 194 195 try { 196 mService.clearVcnConfig(subscriptionGroup, mContext.getOpPackageName()); 197 } catch (ServiceSpecificException e) { 198 throw new IOException(e); 199 } catch (RemoteException e) { 200 throw e.rethrowFromSystemServer(); 201 } 202 } 203 204 /** 205 * Retrieves the list of Subscription Groups for which a VCN Configuration has been set. 206 * 207 * <p>The returned list will include only subscription groups for which an associated {@link 208 * VcnConfig} exists, and the app is either: 209 * 210 * <ul> 211 * <li>Carrier privileged for that subscription group, or 212 * <li>Is the provisioning package of the config 213 * </ul> 214 * 215 * @throws SecurityException if the caller is not running as the primary user 216 */ 217 @NonNull getConfiguredSubscriptionGroups()218 public List<ParcelUuid> getConfiguredSubscriptionGroups() { 219 try { 220 return mService.getConfiguredSubscriptionGroups(mContext.getOpPackageName()); 221 } catch (RemoteException e) { 222 throw e.rethrowFromSystemServer(); 223 } 224 } 225 226 // TODO(b/180537630): remove all VcnUnderlyingNetworkPolicyListener refs once Telephony is using 227 // the new VcnNetworkPolicyChangeListener API 228 /** 229 * VcnUnderlyingNetworkPolicyListener is the interface through which internal system components 230 * can register to receive updates for VCN-underlying Network policies from the System Server. 231 * 232 * @hide 233 */ 234 public interface VcnUnderlyingNetworkPolicyListener extends VcnNetworkPolicyChangeListener {} 235 236 /** 237 * Add a listener for VCN-underlying network policy updates. 238 * 239 * @param executor the Executor that will be used for invoking all calls to the specified 240 * Listener 241 * @param listener the VcnUnderlyingNetworkPolicyListener to be added 242 * @throws SecurityException if the caller does not have permission NETWORK_FACTORY 243 * @throws IllegalStateException if the specified VcnUnderlyingNetworkPolicyListener is already 244 * registered 245 * @hide 246 */ 247 @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) addVcnUnderlyingNetworkPolicyListener( @onNull Executor executor, @NonNull VcnUnderlyingNetworkPolicyListener listener)248 public void addVcnUnderlyingNetworkPolicyListener( 249 @NonNull Executor executor, @NonNull VcnUnderlyingNetworkPolicyListener listener) { 250 addVcnNetworkPolicyChangeListener(executor, listener); 251 } 252 253 /** 254 * Remove the specified VcnUnderlyingNetworkPolicyListener from VcnManager. 255 * 256 * <p>If the specified listener is not currently registered, this is a no-op. 257 * 258 * @param listener the VcnUnderlyingNetworkPolicyListener that will be removed 259 * @hide 260 */ removeVcnUnderlyingNetworkPolicyListener( @onNull VcnUnderlyingNetworkPolicyListener listener)261 public void removeVcnUnderlyingNetworkPolicyListener( 262 @NonNull VcnUnderlyingNetworkPolicyListener listener) { 263 removeVcnNetworkPolicyChangeListener(listener); 264 } 265 266 /** 267 * Queries the underlying network policy for a network with the given parameters. 268 * 269 * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy 270 * may have changed via {@link VcnUnderlyingNetworkPolicyListener#onPolicyChanged()}, a Network 271 * Provider MUST poll for the updated Network policy based on that Network's capabilities and 272 * properties. 273 * 274 * @param networkCapabilities the NetworkCapabilities to be used in determining the Network 275 * policy for this Network. 276 * @param linkProperties the LinkProperties to be used in determining the Network policy for 277 * this Network. 278 * @throws SecurityException if the caller does not have permission NETWORK_FACTORY 279 * @return the VcnUnderlyingNetworkPolicy to be used for this Network. 280 * @hide 281 */ 282 @NonNull 283 @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) getUnderlyingNetworkPolicy( @onNull NetworkCapabilities networkCapabilities, @NonNull LinkProperties linkProperties)284 public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy( 285 @NonNull NetworkCapabilities networkCapabilities, 286 @NonNull LinkProperties linkProperties) { 287 requireNonNull(networkCapabilities, "networkCapabilities must not be null"); 288 requireNonNull(linkProperties, "linkProperties must not be null"); 289 290 try { 291 return mService.getUnderlyingNetworkPolicy(networkCapabilities, linkProperties); 292 } catch (RemoteException e) { 293 throw e.rethrowFromSystemServer(); 294 } 295 } 296 297 /** 298 * VcnNetworkPolicyChangeListener is the interface through which internal system components 299 * (e.g. Network Factories) can register to receive updates for VCN-underlying Network policies 300 * from the System Server. 301 * 302 * <p>Any Network Factory that brings up Networks capable of being VCN-underlying Networks 303 * should register a VcnNetworkPolicyChangeListener. VcnManager will then use this listener to 304 * notify the registrant when VCN Network policies change. Upon receiving this signal, the 305 * listener must check {@link VcnManager} for the current Network policy result for each of its 306 * Networks via {@link #applyVcnNetworkPolicy(NetworkCapabilities, LinkProperties)}. 307 * 308 * @hide 309 */ 310 @SystemApi 311 public interface VcnNetworkPolicyChangeListener { 312 /** 313 * Notifies the implementation that the VCN's underlying Network policy has changed. 314 * 315 * <p>After receiving this callback, implementations should get the current {@link 316 * VcnNetworkPolicyResult} via {@link #applyVcnNetworkPolicy(NetworkCapabilities, 317 * LinkProperties)}. 318 */ onPolicyChanged()319 void onPolicyChanged(); 320 } 321 322 /** 323 * Add a listener for VCN-underlying Network policy updates. 324 * 325 * <p>A {@link VcnNetworkPolicyChangeListener} is eligible to begin receiving callbacks once it 326 * is registered. No callbacks are guaranteed upon registration. 327 * 328 * @param executor the Executor that will be used for invoking all calls to the specified 329 * Listener 330 * @param listener the VcnNetworkPolicyChangeListener to be added 331 * @throws SecurityException if the caller does not have permission NETWORK_FACTORY 332 * @throws IllegalStateException if the specified VcnNetworkPolicyChangeListener is already 333 * registered 334 * @hide 335 */ 336 @SystemApi 337 @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) addVcnNetworkPolicyChangeListener( @onNull Executor executor, @NonNull VcnNetworkPolicyChangeListener listener)338 public void addVcnNetworkPolicyChangeListener( 339 @NonNull Executor executor, @NonNull VcnNetworkPolicyChangeListener listener) { 340 requireNonNull(executor, "executor must not be null"); 341 requireNonNull(listener, "listener must not be null"); 342 343 VcnUnderlyingNetworkPolicyListenerBinder binder = 344 new VcnUnderlyingNetworkPolicyListenerBinder(executor, listener); 345 if (REGISTERED_POLICY_LISTENERS.putIfAbsent(listener, binder) != null) { 346 throw new IllegalStateException("listener is already registered with VcnManager"); 347 } 348 349 try { 350 mService.addVcnUnderlyingNetworkPolicyListener(binder); 351 } catch (RemoteException e) { 352 REGISTERED_POLICY_LISTENERS.remove(listener); 353 throw e.rethrowFromSystemServer(); 354 } 355 } 356 357 /** 358 * Remove the specified VcnNetworkPolicyChangeListener from VcnManager. 359 * 360 * <p>If the specified listener is not currently registered, this is a no-op. 361 * 362 * @param listener the VcnNetworkPolicyChangeListener that will be removed 363 * @throws SecurityException if the caller does not have permission NETWORK_FACTORY 364 * @hide 365 */ 366 @SystemApi 367 @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) removeVcnNetworkPolicyChangeListener( @onNull VcnNetworkPolicyChangeListener listener)368 public void removeVcnNetworkPolicyChangeListener( 369 @NonNull VcnNetworkPolicyChangeListener listener) { 370 requireNonNull(listener, "listener must not be null"); 371 372 VcnUnderlyingNetworkPolicyListenerBinder binder = 373 REGISTERED_POLICY_LISTENERS.remove(listener); 374 if (binder == null) { 375 return; 376 } 377 378 try { 379 mService.removeVcnUnderlyingNetworkPolicyListener(binder); 380 } catch (RemoteException e) { 381 throw e.rethrowFromSystemServer(); 382 } 383 } 384 385 /** 386 * Applies the network policy for a {@link android.net.Network} with the given parameters. 387 * 388 * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy 389 * may have changed via {@link VcnNetworkPolicyChangeListener#onPolicyChanged()}, a Network 390 * Provider MUST poll for the updated Network policy based on that Network's capabilities and 391 * properties. 392 * 393 * @param networkCapabilities the NetworkCapabilities to be used in determining the Network 394 * policy result for this Network. 395 * @param linkProperties the LinkProperties to be used in determining the Network policy result 396 * for this Network. 397 * @throws SecurityException if the caller does not have permission NETWORK_FACTORY 398 * @return the {@link VcnNetworkPolicyResult} to be used for this Network. 399 * @hide 400 */ 401 @NonNull 402 @SystemApi 403 @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) applyVcnNetworkPolicy( @onNull NetworkCapabilities networkCapabilities, @NonNull LinkProperties linkProperties)404 public VcnNetworkPolicyResult applyVcnNetworkPolicy( 405 @NonNull NetworkCapabilities networkCapabilities, 406 @NonNull LinkProperties linkProperties) { 407 requireNonNull(networkCapabilities, "networkCapabilities must not be null"); 408 requireNonNull(linkProperties, "linkProperties must not be null"); 409 410 final VcnUnderlyingNetworkPolicy policy = 411 getUnderlyingNetworkPolicy(networkCapabilities, linkProperties); 412 return new VcnNetworkPolicyResult( 413 policy.isTeardownRequested(), policy.getMergedNetworkCapabilities()); 414 } 415 416 /** @hide */ 417 @Retention(RetentionPolicy.SOURCE) 418 @IntDef({ 419 VCN_STATUS_CODE_NOT_CONFIGURED, 420 VCN_STATUS_CODE_INACTIVE, 421 VCN_STATUS_CODE_ACTIVE, 422 VCN_STATUS_CODE_SAFE_MODE 423 }) 424 public @interface VcnStatusCode {} 425 426 /** 427 * Value indicating that the VCN for the subscription group is not configured, or that the 428 * callback is not privileged for the subscription group. 429 */ 430 public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; 431 432 /** 433 * Value indicating that the VCN for the subscription group is inactive. 434 * 435 * <p>A VCN is inactive if a {@link VcnConfig} is present for the subscription group, but the 436 * provisioning package is not privileged. 437 */ 438 public static final int VCN_STATUS_CODE_INACTIVE = 1; 439 440 /** 441 * Value indicating that the VCN for the subscription group is active. 442 * 443 * <p>A VCN is active if a {@link VcnConfig} is present for the subscription, the provisioning 444 * package is privileged, and the VCN is not in Safe Mode. In other words, a VCN is considered 445 * active while it is connecting, fully connected, and disconnecting. 446 */ 447 public static final int VCN_STATUS_CODE_ACTIVE = 2; 448 449 /** 450 * Value indicating that the VCN for the subscription group is in Safe Mode. 451 * 452 * <p>A VCN will be put into Safe Mode if any of the gateway connections were unable to 453 * establish a connection within a system-determined timeout (while underlying networks were 454 * available). 455 */ 456 public static final int VCN_STATUS_CODE_SAFE_MODE = 3; 457 458 /** @hide */ 459 @Retention(RetentionPolicy.SOURCE) 460 @IntDef({ 461 VCN_ERROR_CODE_INTERNAL_ERROR, 462 VCN_ERROR_CODE_CONFIG_ERROR, 463 VCN_ERROR_CODE_NETWORK_ERROR 464 }) 465 public @interface VcnErrorCode {} 466 467 /** 468 * Value indicating that an internal failure occurred in this Gateway Connection. 469 */ 470 public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; 471 472 /** 473 * Value indicating that an error with this Gateway Connection's configuration occurred. 474 * 475 * <p>For example, this error code will be returned after authentication failures. 476 */ 477 public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; 478 479 /** 480 * Value indicating that a Network error occurred with this Gateway Connection. 481 * 482 * <p>For example, this error code will be returned if an underlying {@link android.net.Network} 483 * for this Gateway Connection is lost, or if an error occurs while resolving the connection 484 * endpoint address. 485 */ 486 public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; 487 488 /** 489 * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs. 490 * 491 * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a 492 * subscription group. 493 */ 494 public abstract static class VcnStatusCallback { 495 private VcnStatusCallbackBinder mCbBinder; 496 497 /** 498 * Invoked when status of the VCN for this callback's subscription group changes. 499 * 500 * @param statusCode the code for the status change encountered by this {@link 501 * VcnStatusCallback}'s subscription group. This value will be one of VCN_STATUS_CODE_*. 502 */ onStatusChanged(@cnStatusCode int statusCode)503 public abstract void onStatusChanged(@VcnStatusCode int statusCode); 504 505 /** 506 * Invoked when a VCN Gateway Connection corresponding to this callback's subscription group 507 * encounters an error. 508 * 509 * @param gatewayConnectionName the String GatewayConnection name for the GatewayConnection 510 * encountering an error. This will match the name for exactly one {@link 511 * VcnGatewayConnectionConfig} for the {@link VcnConfig} configured for this callback's 512 * subscription group 513 * @param errorCode the code to indicate the error that occurred. This value will be one of 514 * VCN_ERROR_CODE_*. 515 * @param detail Throwable to provide additional information about the error, or {@code 516 * null} if none 517 */ onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable Throwable detail)518 public abstract void onGatewayConnectionError( 519 @NonNull String gatewayConnectionName, 520 @VcnErrorCode int errorCode, 521 @Nullable Throwable detail); 522 } 523 524 /** 525 * Registers the given callback to receive status updates for the specified subscription. 526 * 527 * <p>Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it. 528 * 529 * <p>A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link 530 * VcnStatusCallback}s may be reused once unregistered. 531 * 532 * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier 533 * privileges for the specified subscription at the time of invocation. 534 * 535 * <p>A {@link VcnStatusCallback} is eligible to begin receiving callbacks once it is registered 536 * and there is a VCN active for its specified subscription group (this may happen after the 537 * callback is registered). 538 * 539 * <p>{@link VcnStatusCallback#onStatusChanged(int)} will be invoked on registration with the 540 * current status for the specified subscription group's VCN. If the registrant is not 541 * privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be 542 * returned. 543 * 544 * @param subscriptionGroup The subscription group to match for callbacks 545 * @param executor The {@link Executor} to be used for invoking callbacks 546 * @param callback The VcnStatusCallback to be registered 547 * @throws IllegalStateException if callback is currently registered with VcnManager 548 */ registerVcnStatusCallback( @onNull ParcelUuid subscriptionGroup, @NonNull Executor executor, @NonNull VcnStatusCallback callback)549 public void registerVcnStatusCallback( 550 @NonNull ParcelUuid subscriptionGroup, 551 @NonNull Executor executor, 552 @NonNull VcnStatusCallback callback) { 553 requireNonNull(subscriptionGroup, "subscriptionGroup must not be null"); 554 requireNonNull(executor, "executor must not be null"); 555 requireNonNull(callback, "callback must not be null"); 556 557 synchronized (callback) { 558 if (callback.mCbBinder != null) { 559 throw new IllegalStateException("callback is already registered with VcnManager"); 560 } 561 callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback); 562 563 try { 564 mService.registerVcnStatusCallback( 565 subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName()); 566 } catch (RemoteException e) { 567 callback.mCbBinder = null; 568 throw e.rethrowFromSystemServer(); 569 } 570 } 571 } 572 573 /** 574 * Unregisters the given callback. 575 * 576 * <p>Once unregistered, the callback will stop receiving status updates for the subscription it 577 * was registered with. 578 * 579 * @param callback The callback to be unregistered 580 */ unregisterVcnStatusCallback(@onNull VcnStatusCallback callback)581 public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) { 582 requireNonNull(callback, "callback must not be null"); 583 584 synchronized (callback) { 585 if (callback.mCbBinder == null) { 586 // no Binder attached to this callback, so it's not currently registered 587 return; 588 } 589 590 try { 591 mService.unregisterVcnStatusCallback(callback.mCbBinder); 592 } catch (RemoteException e) { 593 throw e.rethrowFromSystemServer(); 594 } finally { 595 callback.mCbBinder = null; 596 } 597 } 598 } 599 600 /** 601 * Binder wrapper for added VcnNetworkPolicyChangeListeners to receive signals from System 602 * Server. 603 * 604 * @hide 605 */ 606 private static class VcnUnderlyingNetworkPolicyListenerBinder 607 extends IVcnUnderlyingNetworkPolicyListener.Stub { 608 @NonNull private final Executor mExecutor; 609 @NonNull private final VcnNetworkPolicyChangeListener mListener; 610 VcnUnderlyingNetworkPolicyListenerBinder( Executor executor, VcnNetworkPolicyChangeListener listener)611 private VcnUnderlyingNetworkPolicyListenerBinder( 612 Executor executor, VcnNetworkPolicyChangeListener listener) { 613 mExecutor = executor; 614 mListener = listener; 615 } 616 617 @Override onPolicyChanged()618 public void onPolicyChanged() { 619 Binder.withCleanCallingIdentity( 620 () -> mExecutor.execute(() -> mListener.onPolicyChanged())); 621 } 622 } 623 624 /** 625 * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService. 626 * 627 * @hide 628 */ 629 @VisibleForTesting(visibility = Visibility.PRIVATE) 630 public static class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub { 631 @NonNull private final Executor mExecutor; 632 @NonNull private final VcnStatusCallback mCallback; 633 VcnStatusCallbackBinder( @onNull Executor executor, @NonNull VcnStatusCallback callback)634 public VcnStatusCallbackBinder( 635 @NonNull Executor executor, @NonNull VcnStatusCallback callback) { 636 mExecutor = executor; 637 mCallback = callback; 638 } 639 640 @Override onVcnStatusChanged(@cnStatusCode int statusCode)641 public void onVcnStatusChanged(@VcnStatusCode int statusCode) { 642 Binder.withCleanCallingIdentity( 643 () -> mExecutor.execute(() -> mCallback.onStatusChanged(statusCode))); 644 } 645 646 // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling' 647 @Override onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)648 public void onGatewayConnectionError( 649 @NonNull String gatewayConnectionName, 650 @VcnErrorCode int errorCode, 651 @Nullable String exceptionClass, 652 @Nullable String exceptionMessage) { 653 final Throwable cause = createThrowableByClassName(exceptionClass, exceptionMessage); 654 655 Binder.withCleanCallingIdentity( 656 () -> 657 mExecutor.execute( 658 () -> 659 mCallback.onGatewayConnectionError( 660 gatewayConnectionName, errorCode, cause))); 661 } 662 createThrowableByClassName( @ullable String className, @Nullable String message)663 private static Throwable createThrowableByClassName( 664 @Nullable String className, @Nullable String message) { 665 if (className == null) { 666 return null; 667 } 668 669 try { 670 Class<?> c = Class.forName(className); 671 return (Throwable) c.getConstructor(String.class).newInstance(message); 672 } catch (ReflectiveOperationException | ClassCastException e) { 673 return new RuntimeException(className + ": " + message); 674 } 675 } 676 } 677 } 678