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 
17 package com.android.server.vcn;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
21 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
22 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
23 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
24 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
25 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
26 
27 import static com.android.server.VcnManagementService.LOCAL_LOG;
28 import static com.android.server.VcnManagementService.VDBG;
29 
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.content.ContentResolver;
33 import android.database.ContentObserver;
34 import android.net.NetworkCapabilities;
35 import android.net.NetworkRequest;
36 import android.net.NetworkScore;
37 import android.net.Uri;
38 import android.net.vcn.VcnConfig;
39 import android.net.vcn.VcnGatewayConnectionConfig;
40 import android.net.vcn.VcnManager.VcnErrorCode;
41 import android.os.Handler;
42 import android.os.HandlerExecutor;
43 import android.os.Message;
44 import android.os.ParcelUuid;
45 import android.provider.Settings;
46 import android.telephony.TelephonyCallback;
47 import android.telephony.TelephonyManager;
48 import android.util.ArrayMap;
49 import android.util.ArraySet;
50 import android.util.Slog;
51 
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.internal.annotations.VisibleForTesting.Visibility;
54 import com.android.internal.util.IndentingPrintWriter;
55 import com.android.server.VcnManagementService.VcnCallback;
56 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
57 import com.android.server.vcn.util.LogUtils;
58 
59 import java.util.Arrays;
60 import java.util.Collections;
61 import java.util.HashMap;
62 import java.util.HashSet;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Map.Entry;
67 import java.util.Objects;
68 import java.util.Set;
69 
70 /**
71  * Represents an single instance of a VCN.
72  *
73  * <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group,
74  * including per-capability networks, network selection, and multi-homing.
75  *
76  * @hide
77  */
78 public class Vcn extends Handler {
79     private static final String TAG = Vcn.class.getSimpleName();
80 
81     private static final int VCN_LEGACY_SCORE_INT = 52;
82 
83     private static final List<Integer> CAPS_REQUIRING_MOBILE_DATA =
84             Arrays.asList(NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN);
85 
86     private static final int MSG_EVENT_BASE = 0;
87     private static final int MSG_CMD_BASE = 100;
88 
89     /**
90      * A carrier app updated the configuration.
91      *
92      * <p>Triggers update of config, re-evaluating all active and underlying networks.
93      *
94      * @param obj VcnConfig
95      */
96     private static final int MSG_EVENT_CONFIG_UPDATED = MSG_EVENT_BASE;
97 
98     /**
99      * A NetworkRequest was added or updated.
100      *
101      * <p>Triggers an evaluation of all active networks, bringing up a new one if necessary.
102      *
103      * @param obj NetworkRequest
104      */
105     private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1;
106 
107     /**
108      * The TelephonySubscriptionSnapshot tracked by VcnManagementService has changed.
109      *
110      * <p>This updated snapshot should be cached locally and passed to all VcnGatewayConnections.
111      *
112      * @param obj TelephonySubscriptionSnapshot
113      */
114     private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2;
115 
116     /**
117      * A GatewayConnection owned by this VCN quit.
118      *
119      * @param obj VcnGatewayConnectionConfig
120      */
121     private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3;
122 
123     /**
124      * Triggers reevaluation of safe mode conditions.
125      *
126      * <p>Upon entering safe mode, the VCN will only provide gateway connections opportunistically,
127      * leaving the underlying networks marked as NOT_VCN_MANAGED.
128      *
129      * <p>Any VcnGatewayConnection in safe mode will result in the entire Vcn instance being put
130      * into safe mode. Upon receiving this message, the Vcn MUST query all VcnGatewayConnections to
131      * determine if any are in safe mode.
132      */
133     private static final int MSG_EVENT_SAFE_MODE_STATE_CHANGED = MSG_EVENT_BASE + 4;
134 
135     /**
136      * Triggers reevaluation of mobile data enabled conditions.
137      *
138      * <p>Upon this notification, the VCN will check if any of the underlying subIds have mobile
139      * data enabled. If not, the VCN will restart any GatewayConnections providing INTERNET or DUN
140      * with the current mobile data toggle status.
141      */
142     private static final int MSG_EVENT_MOBILE_DATA_TOGGLED = MSG_EVENT_BASE + 5;
143 
144     /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
145     private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
146 
147     @NonNull private final VcnContext mVcnContext;
148     @NonNull private final ParcelUuid mSubscriptionGroup;
149     @NonNull private final Dependencies mDeps;
150     @NonNull private final VcnNetworkRequestListener mRequestListener;
151     @NonNull private final VcnCallback mVcnCallback;
152     @NonNull private final VcnContentResolver mContentResolver;
153     @NonNull private final ContentObserver mMobileDataSettingsObserver;
154 
155     @NonNull
156     private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners =
157             new ArrayMap<>();
158 
159     /**
160      * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
161      *
162      * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be created and added
163      * to this map in {@link #handleNetworkRequested(NetworkRequest, int, int)}, when a VCN receives
164      * a NetworkRequest that matches a VcnGatewayConnectionConfig for this VCN's VcnConfig.
165      *
166      * <p>A VcnGatewayConnection instance MUST NEVER overwrite an existing instance - otherwise
167      * there is potential for a orphaned VcnGatewayConnection instance that does not get properly
168      * shut down.
169      *
170      * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be removed from this
171      * map once they have finished tearing down, which is reported to this VCN via {@link
172      * VcnGatewayStatusCallback#onQuit()}. Once this is done, all NetworkRequests are retrieved from
173      * the NetworkProvider so that another VcnGatewayConnectionConfig can match the
174      * previously-matched request.
175      */
176     // TODO(b/182533200): remove the invariant on VcnGatewayConnection lifecycles
177     @NonNull
178     private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
179             new HashMap<>();
180 
181     @NonNull private VcnConfig mConfig;
182     @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
183 
184     /**
185      * The current status of this Vcn instance
186      *
187      * <p>The value will be {@link VCN_STATUS_CODE_ACTIVE} while all VcnGatewayConnections are in
188      * good standing, {@link VCN_STATUS_CODE_SAFE_MODE} if any VcnGatewayConnections are in safe
189      * mode, and {@link VCN_STATUS_CODE_INACTIVE} once a teardown has been commanded.
190      */
191     // Accessed from different threads, but always under lock in VcnManagementService
192     private volatile int mCurrentStatus = VCN_STATUS_CODE_ACTIVE;
193 
194     private boolean mIsMobileDataEnabled = false;
195 
Vcn( @onNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnCallback vcnCallback)196     public Vcn(
197             @NonNull VcnContext vcnContext,
198             @NonNull ParcelUuid subscriptionGroup,
199             @NonNull VcnConfig config,
200             @NonNull TelephonySubscriptionSnapshot snapshot,
201             @NonNull VcnCallback vcnCallback) {
202         this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies());
203     }
204 
205     @VisibleForTesting(visibility = Visibility.PRIVATE)
Vcn( @onNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnCallback vcnCallback, @NonNull Dependencies deps)206     public Vcn(
207             @NonNull VcnContext vcnContext,
208             @NonNull ParcelUuid subscriptionGroup,
209             @NonNull VcnConfig config,
210             @NonNull TelephonySubscriptionSnapshot snapshot,
211             @NonNull VcnCallback vcnCallback,
212             @NonNull Dependencies deps) {
213         super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
214         mVcnContext = vcnContext;
215         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
216         mVcnCallback = Objects.requireNonNull(vcnCallback, "Missing vcnCallback");
217         mDeps = Objects.requireNonNull(deps, "Missing deps");
218         mRequestListener = new VcnNetworkRequestListener();
219         mContentResolver = mDeps.newVcnContentResolver(mVcnContext);
220         mMobileDataSettingsObserver = new VcnMobileDataContentObserver(this /* handler */);
221 
222         final Uri uri = Settings.Global.getUriFor(Settings.Global.MOBILE_DATA);
223         mContentResolver.registerContentObserver(
224                 uri, true /* notifyForDescendants */, mMobileDataSettingsObserver);
225 
226         mConfig = Objects.requireNonNull(config, "Missing config");
227         mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
228 
229         // Update mIsMobileDataEnabled before starting handling of NetworkRequests.
230         mIsMobileDataEnabled = getMobileDataStatus();
231 
232         // Register mobile data state listeners.
233         updateMobileDataStateListeners();
234 
235         // Register to receive cached and future NetworkRequests
236         mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
237     }
238 
239     /** Asynchronously updates the configuration and triggers a re-evaluation of Networks */
updateConfig(@onNull VcnConfig config)240     public void updateConfig(@NonNull VcnConfig config) {
241         Objects.requireNonNull(config, "Missing config");
242 
243         sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config));
244     }
245 
246     /** Asynchronously updates the Subscription snapshot for this VCN. */
updateSubscriptionSnapshot(@onNull TelephonySubscriptionSnapshot snapshot)247     public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
248         Objects.requireNonNull(snapshot, "Missing snapshot");
249 
250         sendMessage(obtainMessage(MSG_EVENT_SUBSCRIPTIONS_CHANGED, snapshot));
251     }
252 
253     /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */
teardownAsynchronously()254     public void teardownAsynchronously() {
255         sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
256     }
257 
258     /** Synchronously retrieves the current status code. */
getStatus()259     public int getStatus() {
260         return mCurrentStatus;
261     }
262 
263     /** Sets the status of this VCN */
264     @VisibleForTesting(visibility = Visibility.PRIVATE)
setStatus(int status)265     public void setStatus(int status) {
266         mCurrentStatus = status;
267     }
268 
269     /** Get current Gateways for testing purposes */
270     @VisibleForTesting(visibility = Visibility.PRIVATE)
getVcnGatewayConnections()271     public Set<VcnGatewayConnection> getVcnGatewayConnections() {
272         return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values()));
273     }
274 
275     /** Get current Configs and Gateways for testing purposes */
276     @VisibleForTesting(visibility = Visibility.PRIVATE)
277     public Map<VcnGatewayConnectionConfig, VcnGatewayConnection>
getVcnGatewayConnectionConfigMap()278             getVcnGatewayConnectionConfigMap() {
279         return Collections.unmodifiableMap(new HashMap<>(mVcnGatewayConnections));
280     }
281 
282     private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
283         @Override
onNetworkRequested(@onNull NetworkRequest request)284         public void onNetworkRequested(@NonNull NetworkRequest request) {
285             Objects.requireNonNull(request, "Missing request");
286 
287             sendMessage(obtainMessage(MSG_EVENT_NETWORK_REQUESTED, request));
288         }
289     }
290 
291     @Override
handleMessage(@onNull Message msg)292     public void handleMessage(@NonNull Message msg) {
293         if (mCurrentStatus != VCN_STATUS_CODE_ACTIVE
294                 && mCurrentStatus != VCN_STATUS_CODE_SAFE_MODE) {
295             return;
296         }
297 
298         switch (msg.what) {
299             case MSG_EVENT_CONFIG_UPDATED:
300                 handleConfigUpdated((VcnConfig) msg.obj);
301                 break;
302             case MSG_EVENT_NETWORK_REQUESTED:
303                 handleNetworkRequested((NetworkRequest) msg.obj);
304                 break;
305             case MSG_EVENT_SUBSCRIPTIONS_CHANGED:
306                 handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj);
307                 break;
308             case MSG_EVENT_GATEWAY_CONNECTION_QUIT:
309                 handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj);
310                 break;
311             case MSG_EVENT_SAFE_MODE_STATE_CHANGED:
312                 handleSafeModeStatusChanged();
313                 break;
314             case MSG_EVENT_MOBILE_DATA_TOGGLED:
315                 handleMobileDataToggled();
316                 break;
317             case MSG_CMD_TEARDOWN:
318                 handleTeardown();
319                 break;
320             default:
321                 logWtf("Unknown msg.what: " + msg.what);
322         }
323     }
324 
handleConfigUpdated(@onNull VcnConfig config)325     private void handleConfigUpdated(@NonNull VcnConfig config) {
326         // TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode()
327         logDbg("Config updated: old = " + mConfig.hashCode() + "; new = " + config.hashCode());
328 
329         mConfig = config;
330 
331         // Teardown any GatewayConnections whose configs have been removed and get all current
332         // requests
333         for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
334                 mVcnGatewayConnections.entrySet()) {
335             final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
336             final VcnGatewayConnection gatewayConnection = entry.getValue();
337 
338             // GatewayConnectionConfigs must match exactly (otherwise authentication or
339             // connection details may have changed).
340             if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
341                 if (gatewayConnection == null) {
342                     logWtf("Found gatewayConnectionConfig without GatewayConnection");
343                 } else {
344                     gatewayConnection.teardownAsynchronously();
345                 }
346             }
347         }
348 
349         // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
350         // satisfied start a new GatewayConnection)
351         mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
352     }
353 
handleTeardown()354     private void handleTeardown() {
355         logDbg("Tearing down");
356         mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener);
357 
358         for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
359             gatewayConnection.teardownAsynchronously();
360         }
361 
362         // Unregister MobileDataStateListeners
363         for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) {
364             getTelephonyManager().unregisterTelephonyCallback(listener);
365         }
366         mMobileDataStateListeners.clear();
367 
368         mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
369     }
370 
handleSafeModeStatusChanged()371     private void handleSafeModeStatusChanged() {
372         logVdbg("VcnGatewayConnection safe mode status changed");
373         boolean hasSafeModeGatewayConnection = false;
374 
375         // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
376         for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
377             if (gatewayConnection.isInSafeMode()) {
378                 hasSafeModeGatewayConnection = true;
379                 break;
380             }
381         }
382 
383         final int oldStatus = mCurrentStatus;
384         mCurrentStatus =
385                 hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
386         if (oldStatus != mCurrentStatus) {
387             mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection);
388             logInfo(
389                     "Safe mode "
390                             + (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited"));
391         }
392     }
393 
handleNetworkRequested(@onNull NetworkRequest request)394     private void handleNetworkRequested(@NonNull NetworkRequest request) {
395         logVdbg("Received request " + request);
396 
397         // If preexisting VcnGatewayConnection(s) satisfy request, return
398         for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
399             if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
400                 logDbg("Request already satisfied by existing VcnGatewayConnection: " + request);
401                 return;
402             }
403         }
404 
405         // If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it
406         // up
407         for (VcnGatewayConnectionConfig gatewayConnectionConfig :
408                 mConfig.getGatewayConnectionConfigs()) {
409             if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
410                 logDbg("Bringing up new VcnGatewayConnection for request " + request);
411 
412                 if (getExposedCapabilitiesForMobileDataState(gatewayConnectionConfig).isEmpty()) {
413                     // Skip; this network does not provide any services if mobile data is disabled.
414                     continue;
415                 }
416 
417                 // This should never happen, by virtue of checking for the above check for
418                 // pre-existing VcnGatewayConnections that satisfy a given request, but if state
419                 // that affects the satsifying of requests changes, this is theoretically possible.
420                 if (mVcnGatewayConnections.containsKey(gatewayConnectionConfig)) {
421                     logWtf(
422                             "Attempted to bring up VcnGatewayConnection for config "
423                                     + "with existing VcnGatewayConnection");
424                     return;
425                 }
426 
427                 final VcnGatewayConnection vcnGatewayConnection =
428                         mDeps.newVcnGatewayConnection(
429                                 mVcnContext,
430                                 mSubscriptionGroup,
431                                 mLastSnapshot,
432                                 gatewayConnectionConfig,
433                                 new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig),
434                                 mIsMobileDataEnabled);
435                 mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
436 
437                 return;
438             }
439         }
440 
441         logVdbg("Request could not be fulfilled by VCN: " + request);
442     }
443 
getExposedCapabilitiesForMobileDataState( VcnGatewayConnectionConfig gatewayConnectionConfig)444     private Set<Integer> getExposedCapabilitiesForMobileDataState(
445             VcnGatewayConnectionConfig gatewayConnectionConfig) {
446         if (mIsMobileDataEnabled) {
447             return gatewayConnectionConfig.getAllExposedCapabilities();
448         }
449 
450         final Set<Integer> exposedCapsWithoutMobileData =
451                 new ArraySet<>(gatewayConnectionConfig.getAllExposedCapabilities());
452         exposedCapsWithoutMobileData.removeAll(CAPS_REQUIRING_MOBILE_DATA);
453 
454         return exposedCapsWithoutMobileData;
455     }
456 
handleGatewayConnectionQuit(VcnGatewayConnectionConfig config)457     private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) {
458         logDbg("VcnGatewayConnection quit: " + config);
459         mVcnGatewayConnections.remove(config);
460 
461         // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
462         // start a new GatewayConnection). VCN is always alive here, courtesy of the liveness check
463         // in handleMessage()
464         mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
465     }
466 
handleSubscriptionsChanged(@onNull TelephonySubscriptionSnapshot snapshot)467     private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
468         mLastSnapshot = snapshot;
469 
470         for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
471             gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
472         }
473 
474         updateMobileDataStateListeners();
475 
476         // Update the mobile data state after updating the subscription snapshot as a change in
477         // subIds for a subGroup may affect the mobile data state.
478         handleMobileDataToggled();
479     }
480 
updateMobileDataStateListeners()481     private void updateMobileDataStateListeners() {
482         final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
483         final HandlerExecutor executor = new HandlerExecutor(this);
484 
485         // Register new callbacks
486         for (int subId : subIdsInGroup) {
487             if (!mMobileDataStateListeners.containsKey(subId)) {
488                 final VcnUserMobileDataStateListener listener =
489                         new VcnUserMobileDataStateListener();
490 
491                 getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener);
492                 mMobileDataStateListeners.put(subId, listener);
493             }
494         }
495 
496         // Unregister old callbacks
497         Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator =
498                 mMobileDataStateListeners.entrySet().iterator();
499         while (iterator.hasNext()) {
500             final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next();
501             if (!subIdsInGroup.contains(entry.getKey())) {
502                 getTelephonyManager().unregisterTelephonyCallback(entry.getValue());
503                 iterator.remove();
504             }
505         }
506     }
507 
handleMobileDataToggled()508     private void handleMobileDataToggled() {
509         final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled;
510         mIsMobileDataEnabled = getMobileDataStatus();
511 
512         if (oldMobileDataEnabledStatus != mIsMobileDataEnabled) {
513             // Teardown any GatewayConnections that advertise INTERNET or DUN. If they provide other
514             // services, the VcnGatewayConnections will be restarted without advertising INTERNET or
515             // DUN.
516             for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
517                     mVcnGatewayConnections.entrySet()) {
518                 final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
519                 final VcnGatewayConnection gatewayConnection = entry.getValue();
520 
521                 final Set<Integer> exposedCaps =
522                         gatewayConnectionConfig.getAllExposedCapabilities();
523                 if (exposedCaps.contains(NET_CAPABILITY_INTERNET)
524                         || exposedCaps.contains(NET_CAPABILITY_DUN)) {
525                     if (gatewayConnection == null) {
526                         logWtf("Found gatewayConnectionConfig without" + " GatewayConnection");
527                     } else {
528                         // TODO(b/184868850): Optimize by restarting NetworkAgents without teardown.
529                         gatewayConnection.teardownAsynchronously();
530                     }
531                 }
532             }
533 
534             // Trigger re-evaluation of all requests; mobile data state impacts supported caps.
535             mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
536 
537             logDbg("Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled"));
538         }
539     }
540 
getMobileDataStatus()541     private boolean getMobileDataStatus() {
542         for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
543             if (getTelephonyManagerForSubid(subId).isDataEnabled()) {
544                 return true;
545             }
546         }
547 
548         return false;
549     }
550 
isRequestSatisfiedByGatewayConnectionConfig( @onNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config)551     private boolean isRequestSatisfiedByGatewayConnectionConfig(
552             @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
553         final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
554         builder.addTransportType(TRANSPORT_CELLULAR);
555         builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
556         for (int cap : getExposedCapabilitiesForMobileDataState(config)) {
557             builder.addCapability(cap);
558         }
559 
560         return request.canBeSatisfiedBy(builder.build());
561     }
562 
getTelephonyManager()563     private TelephonyManager getTelephonyManager() {
564         return mVcnContext.getContext().getSystemService(TelephonyManager.class);
565     }
566 
getTelephonyManagerForSubid(int subid)567     private TelephonyManager getTelephonyManagerForSubid(int subid) {
568         return getTelephonyManager().createForSubscriptionId(subid);
569     }
570 
getLogPrefix()571     private String getLogPrefix() {
572         return "["
573                 + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
574                 + "-"
575                 + System.identityHashCode(this)
576                 + "] ";
577     }
578 
logVdbg(String msg)579     private void logVdbg(String msg) {
580         if (VDBG) {
581             Slog.v(TAG, getLogPrefix() + msg);
582         }
583     }
584 
logDbg(String msg)585     private void logDbg(String msg) {
586         Slog.d(TAG, getLogPrefix() + msg);
587     }
588 
logDbg(String msg, Throwable tr)589     private void logDbg(String msg, Throwable tr) {
590         Slog.d(TAG, getLogPrefix() + msg, tr);
591     }
592 
logInfo(String msg)593     private void logInfo(String msg) {
594         Slog.i(TAG, getLogPrefix() + msg);
595         LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg);
596     }
597 
logInfo(String msg, Throwable tr)598     private void logInfo(String msg, Throwable tr) {
599         Slog.i(TAG, getLogPrefix() + msg, tr);
600         LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg + tr);
601     }
602 
logErr(String msg)603     private void logErr(String msg) {
604         Slog.e(TAG, getLogPrefix() + msg);
605         LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg);
606     }
607 
logErr(String msg, Throwable tr)608     private void logErr(String msg, Throwable tr) {
609         Slog.e(TAG, getLogPrefix() + msg, tr);
610         LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg + tr);
611     }
612 
logWtf(String msg)613     private void logWtf(String msg) {
614         Slog.wtf(TAG, getLogPrefix() + msg);
615         LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg);
616     }
617 
logWtf(String msg, Throwable tr)618     private void logWtf(String msg, Throwable tr) {
619         Slog.wtf(TAG, getLogPrefix() + msg, tr);
620         LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg + tr);
621     }
622 
623     /**
624      * Dumps the state of this Vcn for logging and debugging purposes.
625      *
626      * <p>PII and credentials MUST NEVER be dumped here.
627      */
dump(IndentingPrintWriter pw)628     public void dump(IndentingPrintWriter pw) {
629         pw.println("Vcn (" + mSubscriptionGroup + "):");
630         pw.increaseIndent();
631 
632         pw.println("mCurrentStatus: " + mCurrentStatus);
633         pw.println("mIsMobileDataEnabled: " + mIsMobileDataEnabled);
634         pw.println();
635 
636         pw.println("mVcnGatewayConnections:");
637         pw.increaseIndent();
638         for (VcnGatewayConnection gw : mVcnGatewayConnections.values()) {
639             gw.dump(pw);
640         }
641         pw.decreaseIndent();
642         pw.println();
643 
644         pw.decreaseIndent();
645     }
646 
647     @VisibleForTesting(visibility = Visibility.PRIVATE)
isMobileDataEnabled()648     public boolean isMobileDataEnabled() {
649         return mIsMobileDataEnabled;
650     }
651 
652     @VisibleForTesting(visibility = Visibility.PRIVATE)
setMobileDataEnabled(boolean isMobileDataEnabled)653     public void setMobileDataEnabled(boolean isMobileDataEnabled) {
654         mIsMobileDataEnabled = isMobileDataEnabled;
655     }
656 
657     /** Retrieves the network score for a VCN Network */
658     // Package visibility for use in VcnGatewayConnection and VcnNetworkProvider
getNetworkScore()659     static NetworkScore getNetworkScore() {
660         // TODO(b/193687515): Stop setting TRANSPORT_PRIMARY, define a TRANSPORT_VCN, and set in
661         //                    NetworkOffer/NetworkAgent.
662         return new NetworkScore.Builder()
663                 .setLegacyInt(VCN_LEGACY_SCORE_INT)
664                 .setTransportPrimary(true)
665                 .build();
666     }
667 
668     /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
669     @VisibleForTesting(visibility = Visibility.PACKAGE)
670     public interface VcnGatewayStatusCallback {
671         /** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */
onSafeModeStatusChanged()672         void onSafeModeStatusChanged();
673 
674         /** Callback by a VcnGatewayConnection to indicate that an error occurred. */
onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)675         void onGatewayConnectionError(
676                 @NonNull String gatewayConnectionName,
677                 @VcnErrorCode int errorCode,
678                 @Nullable String exceptionClass,
679                 @Nullable String exceptionMessage);
680 
681         /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */
onQuit()682         void onQuit();
683     }
684 
685     private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
686         public final VcnGatewayConnectionConfig mGatewayConnectionConfig;
687 
VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig)688         VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) {
689             mGatewayConnectionConfig = gatewayConnectionConfig;
690         }
691 
692         @Override
onQuit()693         public void onQuit() {
694             sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig));
695         }
696 
697         @Override
onSafeModeStatusChanged()698         public void onSafeModeStatusChanged() {
699             sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED));
700         }
701 
702         @Override
onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)703         public void onGatewayConnectionError(
704                 @NonNull String gatewayConnectionName,
705                 @VcnErrorCode int errorCode,
706                 @Nullable String exceptionClass,
707                 @Nullable String exceptionMessage) {
708             mVcnCallback.onGatewayConnectionError(
709                     gatewayConnectionName, errorCode, exceptionClass, exceptionMessage);
710         }
711     }
712 
713     private class VcnMobileDataContentObserver extends ContentObserver {
VcnMobileDataContentObserver(Handler handler)714         private VcnMobileDataContentObserver(Handler handler) {
715             super(handler);
716         }
717 
718         @Override
onChange(boolean selfChange)719         public void onChange(boolean selfChange) {
720             sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
721         }
722     }
723 
724     @VisibleForTesting(visibility = Visibility.PRIVATE)
725     class VcnUserMobileDataStateListener extends TelephonyCallback
726             implements TelephonyCallback.UserMobileDataStateListener {
727 
728         @Override
onUserMobileDataStateChanged(boolean enabled)729         public void onUserMobileDataStateChanged(boolean enabled) {
730             sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
731         }
732     }
733 
734     /** External dependencies used by Vcn, for injection in tests */
735     @VisibleForTesting(visibility = Visibility.PRIVATE)
736     public static class Dependencies {
737         /** Builds a new VcnGatewayConnection */
newVcnGatewayConnection( VcnContext vcnContext, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, VcnGatewayConnectionConfig connectionConfig, VcnGatewayStatusCallback gatewayStatusCallback, boolean isMobileDataEnabled)738         public VcnGatewayConnection newVcnGatewayConnection(
739                 VcnContext vcnContext,
740                 ParcelUuid subscriptionGroup,
741                 TelephonySubscriptionSnapshot snapshot,
742                 VcnGatewayConnectionConfig connectionConfig,
743                 VcnGatewayStatusCallback gatewayStatusCallback,
744                 boolean isMobileDataEnabled) {
745             return new VcnGatewayConnection(
746                     vcnContext,
747                     subscriptionGroup,
748                     snapshot,
749                     connectionConfig,
750                     gatewayStatusCallback,
751                     isMobileDataEnabled);
752         }
753 
754         /** Builds a new VcnContentResolver instance */
newVcnContentResolver(VcnContext vcnContext)755         public VcnContentResolver newVcnContentResolver(VcnContext vcnContext) {
756             return new VcnContentResolver(vcnContext);
757         }
758     }
759 
760     /** Proxy Implementation of NetworkAgent, used for testing. */
761     @VisibleForTesting(visibility = Visibility.PRIVATE)
762     public static class VcnContentResolver {
763         private final ContentResolver mImpl;
764 
VcnContentResolver(VcnContext vcnContext)765         public VcnContentResolver(VcnContext vcnContext) {
766             mImpl = vcnContext.getContext().getContentResolver();
767         }
768 
769         /** Registers the content observer */
registerContentObserver( @onNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer)770         public void registerContentObserver(
771                 @NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer) {
772             mImpl.registerContentObserver(uri, notifyForDescendants, observer);
773         }
774     }
775 }
776