• 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     /** 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