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