• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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