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