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