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