| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package android.net.vcn; |
| |
| import static java.util.Objects.requireNonNull; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.RequiresFeature; |
| import android.annotation.RequiresPermission; |
| import android.annotation.SystemApi; |
| import android.annotation.SystemService; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.net.LinkProperties; |
| import android.net.NetworkCapabilities; |
| import android.os.Binder; |
| import android.os.ParcelUuid; |
| import android.os.RemoteException; |
| import android.os.ServiceSpecificException; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.annotations.VisibleForTesting.Visibility; |
| |
| import java.io.IOException; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks. |
| * |
| * <p>A VCN creates a virtualization layer to allow carriers to aggregate heterogeneous physical |
| * networks, unifying them as a single carrier network. This enables infrastructure flexibility on |
| * the part of carriers without impacting user connectivity, abstracting the physical network |
| * technologies as an implementation detail of their public network. |
| * |
| * <p>Each VCN virtualizes a carrier's network by building tunnels to a carrier's core network over |
| * carrier-managed physical links and supports a IP mobility layer to ensure seamless transitions |
| * between the underlying networks. Each VCN is configured based on a Subscription Group (see {@link |
| * android.telephony.SubscriptionManager}) and aggregates all networks that are brought up based on |
| * a profile or suggestion in the specified Subscription Group. |
| * |
| * <p>The VCN can be configured to expose one or more {@link android.net.Network}(s), each with |
| * different capabilities, allowing for APN virtualization. |
| * |
| * <p>If a tunnel fails to connect, or otherwise encounters a fatal error, the VCN will attempt to |
| * reestablish the connection. If the tunnel still has not reconnected after a system-determined |
| * timeout, the VCN Safe Mode (see below) will be entered. |
| * |
| * <p>The VCN Safe Mode ensures that users (and carriers) have a fallback to restore system |
| * connectivity to update profiles, diagnose issues, contact support, or perform other remediation |
| * tasks. In Safe Mode, the system will allow underlying cellular networks to be used as default. |
| * Additionally, during Safe Mode, the VCN will continue to retry the connections, and will |
| * automatically exit Safe Mode if all active tunnels connect successfully. |
| * |
| * <p>Apps targeting Android 15 or newer should check the existence of {@link |
| * PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} before querying the service. If the feature is |
| * absent, {@link Context#getSystemService} may return null. |
| */ |
| @SystemService(Context.VCN_MANAGEMENT_SERVICE) |
| @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) |
| public class VcnManager { |
| @NonNull private static final String TAG = VcnManager.class.getSimpleName(); |
| |
| /** |
| * Key for WiFi entry RSSI thresholds |
| * |
| * <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater |
| * than, or equal to this threshold. |
| * |
| * @hide |
| */ |
| @NonNull |
| public static final String VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY = |
| "vcn_network_selection_wifi_entry_rssi_threshold"; |
| |
| /** |
| * Key for WiFi entry RSSI thresholds |
| * |
| * <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold, |
| * the VCN will attempt to migrate away from the Carrier WiFi network. |
| * |
| * @hide |
| */ |
| @NonNull |
| public static final String VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY = |
| "vcn_network_selection_wifi_exit_rssi_threshold"; |
| |
| /** |
| * Key for the interval to poll IpSecTransformState for packet loss monitoring |
| * |
| * @hide |
| */ |
| @NonNull |
| public static final String VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY = |
| "vcn_network_selection_poll_ipsec_state_interval_seconds"; |
| |
| /** |
| * Key for the threshold of IPSec packet loss rate |
| * |
| * @hide |
| */ |
| @NonNull |
| public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY = |
| "vcn_network_selection_ipsec_packet_loss_percent_threshold"; |
| |
| /** |
| * Key for detecting unusually large increases in IPsec packet sequence numbers. |
| * |
| * <p>If the sequence number increases by more than this value within a second, it may indicate |
| * an intentional leap on the server's downlink. To avoid false positives, the packet loss |
| * detector will suppress loss reporting. |
| * |
| * <p>By default, there's no maximum limit enforced, prioritizing detection of lossy networks. |
| * To reduce false positives, consider setting an appropriate maximum threshold. |
| * |
| * @hide |
| */ |
| @NonNull |
| public static final String VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY = |
| "vcn_network_selection_max_seq_num_increase_per_second"; |
| |
| /** |
| * Key for the list of timeouts in minute to stop penalizing an underlying network candidate |
| * |
| * @hide |
| */ |
| @NonNull |
| public static final String VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY = |
| "vcn_network_selection_penalty_timeout_minutes_list"; |
| |
| // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz |
| |
| /** |
| * Key for transports that need to be marked as restricted by the VCN |
| * |
| * <p>Defaults to TRANSPORT_WIFI if the config does not exist |
| * |
| * @hide |
| */ |
| public static final String VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY = |
| "vcn_restricted_transports"; |
| |
| /** |
| * Key for number of seconds to wait before entering safe mode |
| * |
| * <p>A VcnGatewayConnection will enter safe mode when it takes over the configured timeout to |
| * enter {@link ConnectedState}. |
| * |
| * <p>Defaults to 30, unless overridden by carrier config |
| * |
| * @hide |
| */ |
| @NonNull |
| public static final String VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY = |
| "vcn_safe_mode_timeout_seconds_key"; |
| |
| /** |
| * Key for maximum number of parallel SAs for tunnel aggregation |
| * |
| * <p>If set to a value > 1, multiple tunnels will be set up, and inbound traffic will be |
| * aggregated over the various tunnels. |
| * |
| * <p>Defaults to 1, unless overridden by carrier config |
| * |
| * @hide |
| */ |
| @NonNull |
| public static final String VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY = |
| "vcn_tunnel_aggregation_sa_count_max"; |
| |
| /** List of Carrier Config options to extract from Carrier Config bundles. @hide */ |
| @NonNull |
| public static final String[] VCN_RELATED_CARRIER_CONFIG_KEYS = |
| new String[] { |
| VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, |
| VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, |
| VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY, |
| VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY, |
| VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY, |
| VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY, |
| VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY, |
| VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, |
| VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY, |
| }; |
| |
| private static final Map< |
| VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder> |
| REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>(); |
| |
| @NonNull private final Context mContext; |
| @NonNull private final IVcnManagementService mService; |
| |
| /** |
| * Construct an instance of VcnManager within an application context. |
| * |
| * @param ctx the application context for this manager |
| * @param service the VcnManagementService binder backing this manager |
| * |
| * @hide |
| */ |
| public VcnManager(@NonNull Context ctx, @NonNull IVcnManagementService service) { |
| mContext = requireNonNull(ctx, "missing context"); |
| mService = requireNonNull(service, "missing service"); |
| } |
| |
| /** |
| * Get all currently registered VcnNetworkPolicyChangeListeners for testing purposes. |
| * |
| * @hide |
| */ |
| @VisibleForTesting(visibility = Visibility.PRIVATE) |
| @NonNull |
| public static Map<VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder> |
| getAllPolicyListeners() { |
| return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS); |
| } |
| |
| /** |
| * Sets the VCN configuration for a given subscription group. |
| * |
| * <p>An app that has carrier privileges for any of the subscriptions in the given group may set |
| * a VCN configuration. If a configuration already exists for the given subscription group, it |
| * will be overridden. Any active VCN(s) may be forced to restart to use the new configuration. |
| * |
| * <p>This API is ONLY permitted for callers running as the primary user. |
| * |
| * @param subscriptionGroup the subscription group that the configuration should be applied to |
| * @param config the configuration parameters for the VCN |
| * @throws SecurityException if the caller does not have carrier privileges for the provided |
| * subscriptionGroup, or is not running as the primary user |
| * @throws IOException if the configuration failed to be saved and persisted to disk. This may |
| * occur due to temporary disk errors, or more permanent conditions such as a full disk. |
| */ |
| @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant |
| public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) |
| throws IOException { |
| requireNonNull(subscriptionGroup, "subscriptionGroup was null"); |
| requireNonNull(config, "config was null"); |
| |
| try { |
| mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName()); |
| } catch (ServiceSpecificException e) { |
| throw new IOException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Clears the VCN configuration for a given subscription group. |
| * |
| * <p>An app that has carrier privileges for any of the subscriptions in the given group may |
| * clear a VCN configuration. This API is ONLY permitted for callers running as the primary |
| * user. Any active VCN associated with this configuration will be torn down. |
| * |
| * @param subscriptionGroup the subscription group that the configuration should be applied to |
| * @throws SecurityException if the caller does not have carrier privileges, is not the owner of |
| * the associated configuration, or is not running as the primary user |
| * @throws IOException if the configuration failed to be cleared from disk. This may occur due |
| * to temporary disk errors, or more permanent conditions such as a full disk. |
| */ |
| @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant |
| public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) throws IOException { |
| requireNonNull(subscriptionGroup, "subscriptionGroup was null"); |
| |
| try { |
| mService.clearVcnConfig(subscriptionGroup, mContext.getOpPackageName()); |
| } catch (ServiceSpecificException e) { |
| throw new IOException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Retrieves the list of Subscription Groups for which a VCN Configuration has been set. |
| * |
| * <p>The returned list will include only subscription groups for which an associated {@link |
| * VcnConfig} exists, and the app is either: |
| * |
| * <ul> |
| * <li>Carrier privileged for that subscription group, or |
| * <li>Is the provisioning package of the config |
| * </ul> |
| * |
| * @throws SecurityException if the caller is not running as the primary user |
| */ |
| @NonNull |
| public List<ParcelUuid> getConfiguredSubscriptionGroups() { |
| try { |
| return mService.getConfiguredSubscriptionGroups(mContext.getOpPackageName()); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| // TODO(b/180537630): remove all VcnUnderlyingNetworkPolicyListener refs once Telephony is using |
| // the new VcnNetworkPolicyChangeListener API |
| /** |
| * VcnUnderlyingNetworkPolicyListener is the interface through which internal system components |
| * can register to receive updates for VCN-underlying Network policies from the System Server. |
| * |
| * @hide |
| */ |
| public interface VcnUnderlyingNetworkPolicyListener extends VcnNetworkPolicyChangeListener {} |
| |
| /** |
| * Add a listener for VCN-underlying network policy updates. |
| * |
| * @param executor the Executor that will be used for invoking all calls to the specified |
| * Listener |
| * @param listener the VcnUnderlyingNetworkPolicyListener to be added |
| * @throws SecurityException if the caller does not have permission NETWORK_FACTORY |
| * @throws IllegalStateException if the specified VcnUnderlyingNetworkPolicyListener is already |
| * registered |
| * @hide |
| */ |
| @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) |
| public void addVcnUnderlyingNetworkPolicyListener( |
| @NonNull Executor executor, @NonNull VcnUnderlyingNetworkPolicyListener listener) { |
| addVcnNetworkPolicyChangeListener(executor, listener); |
| } |
| |
| /** |
| * Remove the specified VcnUnderlyingNetworkPolicyListener from VcnManager. |
| * |
| * <p>If the specified listener is not currently registered, this is a no-op. |
| * |
| * @param listener the VcnUnderlyingNetworkPolicyListener that will be removed |
| * @hide |
| */ |
| public void removeVcnUnderlyingNetworkPolicyListener( |
| @NonNull VcnUnderlyingNetworkPolicyListener listener) { |
| removeVcnNetworkPolicyChangeListener(listener); |
| } |
| |
| /** |
| * Queries the underlying network policy for a network with the given parameters. |
| * |
| * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy |
| * may have changed via {@link VcnUnderlyingNetworkPolicyListener#onPolicyChanged()}, a Network |
| * Provider MUST poll for the updated Network policy based on that Network's capabilities and |
| * properties. |
| * |
| * @param networkCapabilities the NetworkCapabilities to be used in determining the Network |
| * policy for this Network. |
| * @param linkProperties the LinkProperties to be used in determining the Network policy for |
| * this Network. |
| * @throws SecurityException if the caller does not have permission NETWORK_FACTORY |
| * @return the VcnUnderlyingNetworkPolicy to be used for this Network. |
| * @hide |
| */ |
| @NonNull |
| @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) |
| public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy( |
| @NonNull NetworkCapabilities networkCapabilities, |
| @NonNull LinkProperties linkProperties) { |
| requireNonNull(networkCapabilities, "networkCapabilities must not be null"); |
| requireNonNull(linkProperties, "linkProperties must not be null"); |
| |
| try { |
| return mService.getUnderlyingNetworkPolicy(networkCapabilities, linkProperties); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * VcnNetworkPolicyChangeListener is the interface through which internal system components |
| * (e.g. Network Factories) can register to receive updates for VCN-underlying Network policies |
| * from the System Server. |
| * |
| * <p>Any Network Factory that brings up Networks capable of being VCN-underlying Networks |
| * should register a VcnNetworkPolicyChangeListener. VcnManager will then use this listener to |
| * notify the registrant when VCN Network policies change. Upon receiving this signal, the |
| * listener must check {@link VcnManager} for the current Network policy result for each of its |
| * Networks via {@link #applyVcnNetworkPolicy(NetworkCapabilities, LinkProperties)}. |
| * |
| * @hide |
| */ |
| @SystemApi |
| public interface VcnNetworkPolicyChangeListener { |
| /** |
| * Notifies the implementation that the VCN's underlying Network policy has changed. |
| * |
| * <p>After receiving this callback, implementations should get the current {@link |
| * VcnNetworkPolicyResult} via {@link #applyVcnNetworkPolicy(NetworkCapabilities, |
| * LinkProperties)}. |
| */ |
| void onPolicyChanged(); |
| } |
| |
| /** |
| * Add a listener for VCN-underlying Network policy updates. |
| * |
| * <p>A {@link VcnNetworkPolicyChangeListener} is eligible to begin receiving callbacks once it |
| * is registered. No callbacks are guaranteed upon registration. |
| * |
| * @param executor the Executor that will be used for invoking all calls to the specified |
| * Listener |
| * @param listener the VcnNetworkPolicyChangeListener to be added |
| * @throws SecurityException if the caller does not have permission NETWORK_FACTORY |
| * @throws IllegalStateException if the specified VcnNetworkPolicyChangeListener is already |
| * registered |
| * @hide |
| */ |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) |
| public void addVcnNetworkPolicyChangeListener( |
| @NonNull Executor executor, @NonNull VcnNetworkPolicyChangeListener listener) { |
| requireNonNull(executor, "executor must not be null"); |
| requireNonNull(listener, "listener must not be null"); |
| |
| VcnUnderlyingNetworkPolicyListenerBinder binder = |
| new VcnUnderlyingNetworkPolicyListenerBinder(executor, listener); |
| if (REGISTERED_POLICY_LISTENERS.putIfAbsent(listener, binder) != null) { |
| throw new IllegalStateException("listener is already registered with VcnManager"); |
| } |
| |
| try { |
| mService.addVcnUnderlyingNetworkPolicyListener(binder); |
| } catch (RemoteException e) { |
| REGISTERED_POLICY_LISTENERS.remove(listener); |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Remove the specified VcnNetworkPolicyChangeListener from VcnManager. |
| * |
| * <p>If the specified listener is not currently registered, this is a no-op. |
| * |
| * @param listener the VcnNetworkPolicyChangeListener that will be removed |
| * @throws SecurityException if the caller does not have permission NETWORK_FACTORY |
| * @hide |
| */ |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) |
| public void removeVcnNetworkPolicyChangeListener( |
| @NonNull VcnNetworkPolicyChangeListener listener) { |
| requireNonNull(listener, "listener must not be null"); |
| |
| VcnUnderlyingNetworkPolicyListenerBinder binder = |
| REGISTERED_POLICY_LISTENERS.remove(listener); |
| if (binder == null) { |
| return; |
| } |
| |
| try { |
| mService.removeVcnUnderlyingNetworkPolicyListener(binder); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Applies the network policy for a {@link android.net.Network} with the given parameters. |
| * |
| * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy |
| * may have changed via {@link VcnNetworkPolicyChangeListener#onPolicyChanged()}, a Network |
| * Provider MUST poll for the updated Network policy based on that Network's capabilities and |
| * properties. |
| * |
| * @param networkCapabilities the NetworkCapabilities to be used in determining the Network |
| * policy result for this Network. |
| * @param linkProperties the LinkProperties to be used in determining the Network policy result |
| * for this Network. |
| * @throws SecurityException if the caller does not have permission NETWORK_FACTORY |
| * @return the {@link VcnNetworkPolicyResult} to be used for this Network. |
| * @hide |
| */ |
| @NonNull |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) |
| public VcnNetworkPolicyResult applyVcnNetworkPolicy( |
| @NonNull NetworkCapabilities networkCapabilities, |
| @NonNull LinkProperties linkProperties) { |
| requireNonNull(networkCapabilities, "networkCapabilities must not be null"); |
| requireNonNull(linkProperties, "linkProperties must not be null"); |
| |
| final VcnUnderlyingNetworkPolicy policy = |
| getUnderlyingNetworkPolicy(networkCapabilities, linkProperties); |
| return new VcnNetworkPolicyResult( |
| policy.isTeardownRequested(), policy.getMergedNetworkCapabilities()); |
| } |
| |
| /** @hide */ |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({ |
| VCN_STATUS_CODE_NOT_CONFIGURED, |
| VCN_STATUS_CODE_INACTIVE, |
| VCN_STATUS_CODE_ACTIVE, |
| VCN_STATUS_CODE_SAFE_MODE |
| }) |
| public @interface VcnStatusCode {} |
| |
| /** |
| * Value indicating that the VCN for the subscription group is not configured, or that the |
| * callback is not privileged for the subscription group. |
| */ |
| public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; |
| |
| /** |
| * Value indicating that the VCN for the subscription group is inactive. |
| * |
| * <p>A VCN is inactive if a {@link VcnConfig} is present for the subscription group, but the |
| * provisioning package is not privileged. |
| */ |
| public static final int VCN_STATUS_CODE_INACTIVE = 1; |
| |
| /** |
| * Value indicating that the VCN for the subscription group is active. |
| * |
| * <p>A VCN is active if a {@link VcnConfig} is present for the subscription, the provisioning |
| * package is privileged, and the VCN is not in Safe Mode. In other words, a VCN is considered |
| * active while it is connecting, fully connected, and disconnecting. |
| */ |
| public static final int VCN_STATUS_CODE_ACTIVE = 2; |
| |
| /** |
| * Value indicating that the VCN for the subscription group is in Safe Mode. |
| * |
| * <p>A VCN will be put into Safe Mode if any of the gateway connections were unable to |
| * establish a connection within a system-determined timeout (while underlying networks were |
| * available). |
| */ |
| public static final int VCN_STATUS_CODE_SAFE_MODE = 3; |
| |
| /** @hide */ |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({ |
| VCN_ERROR_CODE_INTERNAL_ERROR, |
| VCN_ERROR_CODE_CONFIG_ERROR, |
| VCN_ERROR_CODE_NETWORK_ERROR |
| }) |
| public @interface VcnErrorCode {} |
| |
| /** |
| * Value indicating that an internal failure occurred in this Gateway Connection. |
| */ |
| public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; |
| |
| /** |
| * Value indicating that an error with this Gateway Connection's configuration occurred. |
| * |
| * <p>For example, this error code will be returned after authentication failures. |
| */ |
| public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; |
| |
| /** |
| * Value indicating that a Network error occurred with this Gateway Connection. |
| * |
| * <p>For example, this error code will be returned if an underlying {@link android.net.Network} |
| * for this Gateway Connection is lost, or if an error occurs while resolving the connection |
| * endpoint address. |
| */ |
| public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; |
| |
| /** |
| * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs. |
| * |
| * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a |
| * subscription group. |
| */ |
| public abstract static class VcnStatusCallback { |
| private VcnStatusCallbackBinder mCbBinder; |
| |
| /** |
| * Invoked when status of the VCN for this callback's subscription group changes. |
| * |
| * @param statusCode the code for the status change encountered by this {@link |
| * VcnStatusCallback}'s subscription group. This value will be one of VCN_STATUS_CODE_*. |
| */ |
| public abstract void onStatusChanged(@VcnStatusCode int statusCode); |
| |
| /** |
| * Invoked when a VCN Gateway Connection corresponding to this callback's subscription group |
| * encounters an error. |
| * |
| * @param gatewayConnectionName the String GatewayConnection name for the GatewayConnection |
| * encountering an error. This will match the name for exactly one {@link |
| * VcnGatewayConnectionConfig} for the {@link VcnConfig} configured for this callback's |
| * subscription group |
| * @param errorCode the code to indicate the error that occurred. This value will be one of |
| * VCN_ERROR_CODE_*. |
| * @param detail Throwable to provide additional information about the error, or {@code |
| * null} if none |
| */ |
| public abstract void onGatewayConnectionError( |
| @NonNull String gatewayConnectionName, |
| @VcnErrorCode int errorCode, |
| @Nullable Throwable detail); |
| } |
| |
| /** |
| * Registers the given callback to receive status updates for the specified subscription. |
| * |
| * <p>Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it. |
| * |
| * <p>A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link |
| * VcnStatusCallback}s may be reused once unregistered. |
| * |
| * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier |
| * privileges for the specified subscription at the time of invocation. |
| * |
| * <p>A {@link VcnStatusCallback} is eligible to begin receiving callbacks once it is registered |
| * and there is a VCN active for its specified subscription group (this may happen after the |
| * callback is registered). |
| * |
| * <p>{@link VcnStatusCallback#onStatusChanged(int)} will be invoked on registration with the |
| * current status for the specified subscription group's VCN. If the registrant is not |
| * privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be |
| * returned. |
| * |
| * @param subscriptionGroup The subscription group to match for callbacks |
| * @param executor The {@link Executor} to be used for invoking callbacks |
| * @param callback The VcnStatusCallback to be registered |
| * @throws IllegalStateException if callback is currently registered with VcnManager |
| */ |
| public void registerVcnStatusCallback( |
| @NonNull ParcelUuid subscriptionGroup, |
| @NonNull Executor executor, |
| @NonNull VcnStatusCallback callback) { |
| requireNonNull(subscriptionGroup, "subscriptionGroup must not be null"); |
| requireNonNull(executor, "executor must not be null"); |
| requireNonNull(callback, "callback must not be null"); |
| |
| synchronized (callback) { |
| if (callback.mCbBinder != null) { |
| throw new IllegalStateException("callback is already registered with VcnManager"); |
| } |
| callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback); |
| |
| try { |
| mService.registerVcnStatusCallback( |
| subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName()); |
| } catch (RemoteException e) { |
| callback.mCbBinder = null; |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| } |
| |
| /** |
| * Unregisters the given callback. |
| * |
| * <p>Once unregistered, the callback will stop receiving status updates for the subscription it |
| * was registered with. |
| * |
| * @param callback The callback to be unregistered |
| */ |
| public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) { |
| requireNonNull(callback, "callback must not be null"); |
| |
| synchronized (callback) { |
| if (callback.mCbBinder == null) { |
| // no Binder attached to this callback, so it's not currently registered |
| return; |
| } |
| |
| try { |
| mService.unregisterVcnStatusCallback(callback.mCbBinder); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } finally { |
| callback.mCbBinder = null; |
| } |
| } |
| } |
| |
| /** |
| * Binder wrapper for added VcnNetworkPolicyChangeListeners to receive signals from System |
| * Server. |
| * |
| * @hide |
| */ |
| private static class VcnUnderlyingNetworkPolicyListenerBinder |
| extends IVcnUnderlyingNetworkPolicyListener.Stub { |
| @NonNull private final Executor mExecutor; |
| @NonNull private final VcnNetworkPolicyChangeListener mListener; |
| |
| private VcnUnderlyingNetworkPolicyListenerBinder( |
| Executor executor, VcnNetworkPolicyChangeListener listener) { |
| mExecutor = executor; |
| mListener = listener; |
| } |
| |
| @Override |
| public void onPolicyChanged() { |
| Binder.withCleanCallingIdentity( |
| () -> mExecutor.execute(() -> mListener.onPolicyChanged())); |
| } |
| } |
| |
| /** |
| * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService. |
| * |
| * @hide |
| */ |
| @VisibleForTesting(visibility = Visibility.PRIVATE) |
| public static class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub { |
| @NonNull private final Executor mExecutor; |
| @NonNull private final VcnStatusCallback mCallback; |
| |
| public VcnStatusCallbackBinder( |
| @NonNull Executor executor, @NonNull VcnStatusCallback callback) { |
| mExecutor = executor; |
| mCallback = callback; |
| } |
| |
| @Override |
| public void onVcnStatusChanged(@VcnStatusCode int statusCode) { |
| Binder.withCleanCallingIdentity( |
| () -> mExecutor.execute(() -> mCallback.onStatusChanged(statusCode))); |
| } |
| |
| // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling' |
| @Override |
| public void onGatewayConnectionError( |
| @NonNull String gatewayConnectionName, |
| @VcnErrorCode int errorCode, |
| @Nullable String exceptionClass, |
| @Nullable String exceptionMessage) { |
| final Throwable cause = createThrowableByClassName(exceptionClass, exceptionMessage); |
| |
| Binder.withCleanCallingIdentity( |
| () -> |
| mExecutor.execute( |
| () -> |
| mCallback.onGatewayConnectionError( |
| gatewayConnectionName, errorCode, cause))); |
| } |
| |
| private static Throwable createThrowableByClassName( |
| @Nullable String className, @Nullable String message) { |
| if (className == null) { |
| return null; |
| } |
| |
| try { |
| Class<?> c = Class.forName(className); |
| return (Throwable) c.getConstructor(String.class).newInstance(message); |
| } catch (ReflectiveOperationException | ClassCastException e) { |
| return new RuntimeException(className + ": " + message); |
| } |
| } |
| } |
| } |