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                     logInfo(
345                             "Config updated, restarting gateway "
346                                     + gatewayConnection.getLogPrefix());
347                     gatewayConnection.teardownAsynchronously();
348                 }
349             }
350         }
351 
352         // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
353         // satisfied start a new GatewayConnection)
354         mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
355     }
356 
handleTeardown()357     private void handleTeardown() {
358         logDbg("Tearing down");
359         mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener);
360 
361         for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
362             gatewayConnection.teardownAsynchronously();
363         }
364 
365         // Unregister MobileDataStateListeners
366         for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) {
367             getTelephonyManager().unregisterTelephonyCallback(listener);
368         }
369         mMobileDataStateListeners.clear();
370 
371         mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
372     }
373 
handleSafeModeStatusChanged()374     private void handleSafeModeStatusChanged() {
375         logVdbg("VcnGatewayConnection safe mode status changed");
376         boolean hasSafeModeGatewayConnection = false;
377 
378         // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
379         for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
380             if (gatewayConnection.isInSafeMode()) {
381                 hasSafeModeGatewayConnection = true;
382                 break;
383             }
384         }
385 
386         final int oldStatus = mCurrentStatus;
387         mCurrentStatus =
388                 hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
389         if (oldStatus != mCurrentStatus) {
390             mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection);
391             logInfo(
392                     "Safe mode "
393                             + (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited"));
394         }
395     }
396 
handleNetworkRequested(@onNull NetworkRequest request)397     private void handleNetworkRequested(@NonNull NetworkRequest request) {
398         logVdbg("Received request " + request);
399 
400         // If preexisting VcnGatewayConnection(s) satisfy request, return
401         for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
402             if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
403                 logVdbg("Request already satisfied by existing VcnGatewayConnection: " + request);
404                 return;
405             }
406         }
407 
408         // If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it
409         // up
410         for (VcnGatewayConnectionConfig gatewayConnectionConfig :
411                 mConfig.getGatewayConnectionConfigs()) {
412             if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
413                 if (getExposedCapabilitiesForMobileDataState(gatewayConnectionConfig).isEmpty()) {
414                     // Skip; this network does not provide any services if mobile data is disabled.
415                     continue;
416                 }
417 
418                 // This should never happen, by virtue of checking for the above check for
419                 // pre-existing VcnGatewayConnections that satisfy a given request, but if state
420                 // that affects the satsifying of requests changes, this is theoretically possible.
421                 if (mVcnGatewayConnections.containsKey(gatewayConnectionConfig)) {
422                     logWtf(
423                             "Attempted to bring up VcnGatewayConnection for config "
424                                     + "with existing VcnGatewayConnection");
425                     return;
426                 }
427 
428                 logInfo("Bringing up new VcnGatewayConnection for request " + request);
429                 final VcnGatewayConnection vcnGatewayConnection =
430                         mDeps.newVcnGatewayConnection(
431                                 mVcnContext,
432                                 mSubscriptionGroup,
433                                 mLastSnapshot,
434                                 gatewayConnectionConfig,
435                                 new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig),
436                                 mIsMobileDataEnabled);
437                 mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
438 
439                 return;
440             }
441         }
442 
443         logVdbg("Request could not be fulfilled by VCN: " + request);
444     }
445 
getExposedCapabilitiesForMobileDataState( VcnGatewayConnectionConfig gatewayConnectionConfig)446     private Set<Integer> getExposedCapabilitiesForMobileDataState(
447             VcnGatewayConnectionConfig gatewayConnectionConfig) {
448         if (mIsMobileDataEnabled) {
449             return gatewayConnectionConfig.getAllExposedCapabilities();
450         }
451 
452         final Set<Integer> exposedCapsWithoutMobileData =
453                 new ArraySet<>(gatewayConnectionConfig.getAllExposedCapabilities());
454         exposedCapsWithoutMobileData.removeAll(CAPS_REQUIRING_MOBILE_DATA);
455 
456         return exposedCapsWithoutMobileData;
457     }
458 
handleGatewayConnectionQuit(VcnGatewayConnectionConfig config)459     private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) {
460         logInfo("VcnGatewayConnection quit: " + config);
461         mVcnGatewayConnections.remove(config);
462 
463         // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
464         // start a new GatewayConnection). VCN is always alive here, courtesy of the liveness check
465         // in handleMessage()
466         mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
467     }
468 
handleSubscriptionsChanged(@onNull TelephonySubscriptionSnapshot snapshot)469     private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
470         mLastSnapshot = snapshot;
471 
472         for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
473             gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
474         }
475 
476         updateMobileDataStateListeners();
477 
478         // Update the mobile data state after updating the subscription snapshot as a change in
479         // subIds for a subGroup may affect the mobile data state.
480         handleMobileDataToggled();
481     }
482 
updateMobileDataStateListeners()483     private void updateMobileDataStateListeners() {
484         final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
485         final HandlerExecutor executor = new HandlerExecutor(this);
486 
487         // Register new callbacks
488         for (int subId : subIdsInGroup) {
489             if (!mMobileDataStateListeners.containsKey(subId)) {
490                 final VcnUserMobileDataStateListener listener =
491                         new VcnUserMobileDataStateListener();
492 
493                 getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener);
494                 mMobileDataStateListeners.put(subId, listener);
495             }
496         }
497 
498         // Unregister old callbacks
499         Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator =
500                 mMobileDataStateListeners.entrySet().iterator();
501         while (iterator.hasNext()) {
502             final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next();
503             if (!subIdsInGroup.contains(entry.getKey())) {
504                 getTelephonyManager().unregisterTelephonyCallback(entry.getValue());
505                 iterator.remove();
506             }
507         }
508     }
509 
handleMobileDataToggled()510     private void handleMobileDataToggled() {
511         final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled;
512         mIsMobileDataEnabled = getMobileDataStatus();
513 
514         if (oldMobileDataEnabledStatus != mIsMobileDataEnabled) {
515             // Teardown any GatewayConnections that advertise INTERNET or DUN. If they provide other
516             // services, the VcnGatewayConnections will be restarted without advertising INTERNET or
517             // DUN.
518             for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
519                     mVcnGatewayConnections.entrySet()) {
520                 final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
521                 final VcnGatewayConnection gatewayConnection = entry.getValue();
522 
523                 final Set<Integer> exposedCaps =
524                         gatewayConnectionConfig.getAllExposedCapabilities();
525                 if (exposedCaps.contains(NET_CAPABILITY_INTERNET)
526                         || exposedCaps.contains(NET_CAPABILITY_DUN)) {
527                     if (gatewayConnection == null) {
528                         logWtf("Found gatewayConnectionConfig without" + " GatewayConnection");
529                     } else {
530                         // TODO(b/184868850): Optimize by restarting NetworkAgents without teardown.
531                         gatewayConnection.teardownAsynchronously();
532                     }
533                 }
534             }
535 
536             // Trigger re-evaluation of all requests; mobile data state impacts supported caps.
537             mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
538 
539             logInfo("Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled"));
540         }
541     }
542 
getMobileDataStatus()543     private boolean getMobileDataStatus() {
544         for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
545             if (getTelephonyManagerForSubid(subId).isDataEnabled()) {
546                 return true;
547             }
548         }
549 
550         return false;
551     }
552 
isRequestSatisfiedByGatewayConnectionConfig( @onNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config)553     private boolean isRequestSatisfiedByGatewayConnectionConfig(
554             @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
555         final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
556         builder.addTransportType(TRANSPORT_CELLULAR);
557         builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
558         for (int cap : getExposedCapabilitiesForMobileDataState(config)) {
559             builder.addCapability(cap);
560         }
561 
562         return request.canBeSatisfiedBy(builder.build());
563     }
564 
getTelephonyManager()565     private TelephonyManager getTelephonyManager() {
566         return mVcnContext.getContext().getSystemService(TelephonyManager.class);
567     }
568 
getTelephonyManagerForSubid(int subid)569     private TelephonyManager getTelephonyManagerForSubid(int subid) {
570         return getTelephonyManager().createForSubscriptionId(subid);
571     }
572 
getLogPrefix()573     private String getLogPrefix() {
574         return "("
575                 + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
576                 + "-"
577                 + System.identityHashCode(this)
578                 + ") ";
579     }
580 
logVdbg(String msg)581     private void logVdbg(String msg) {
582         if (VDBG) {
583             Slog.v(TAG, getLogPrefix() + msg);
584         }
585     }
586 
logDbg(String msg)587     private void logDbg(String msg) {
588         Slog.d(TAG, getLogPrefix() + msg);
589     }
590 
logDbg(String msg, Throwable tr)591     private void logDbg(String msg, Throwable tr) {
592         Slog.d(TAG, getLogPrefix() + msg, tr);
593     }
594 
logInfo(String msg)595     private void logInfo(String msg) {
596         Slog.i(TAG, getLogPrefix() + msg);
597         LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg);
598     }
599 
logInfo(String msg, Throwable tr)600     private void logInfo(String msg, Throwable tr) {
601         Slog.i(TAG, getLogPrefix() + msg, tr);
602         LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg + tr);
603     }
604 
logErr(String msg)605     private void logErr(String msg) {
606         Slog.e(TAG, getLogPrefix() + msg);
607         LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg);
608     }
609 
logErr(String msg, Throwable tr)610     private void logErr(String msg, Throwable tr) {
611         Slog.e(TAG, getLogPrefix() + msg, tr);
612         LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg + tr);
613     }
614 
logWtf(String msg)615     private void logWtf(String msg) {
616         Slog.wtf(TAG, getLogPrefix() + msg);
617         LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg);
618     }
619 
logWtf(String msg, Throwable tr)620     private void logWtf(String msg, Throwable tr) {
621         Slog.wtf(TAG, getLogPrefix() + msg, tr);
622         LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg + tr);
623     }
624 
625     /**
626      * Dumps the state of this Vcn for logging and debugging purposes.
627      *
628      * <p>PII and credentials MUST NEVER be dumped here.
629      */
dump(IndentingPrintWriter pw)630     public void dump(IndentingPrintWriter pw) {
631         pw.println("Vcn (" + mSubscriptionGroup + "):");
632         pw.increaseIndent();
633 
634         pw.println("mCurrentStatus: " + mCurrentStatus);
635         pw.println("mIsMobileDataEnabled: " + mIsMobileDataEnabled);
636         pw.println();
637 
638         pw.println("mVcnGatewayConnections:");
639         pw.increaseIndent();
640         for (VcnGatewayConnection gw : mVcnGatewayConnections.values()) {
641             gw.dump(pw);
642         }
643         pw.decreaseIndent();
644         pw.println();
645 
646         pw.decreaseIndent();
647     }
648 
649     @VisibleForTesting(visibility = Visibility.PRIVATE)
isMobileDataEnabled()650     public boolean isMobileDataEnabled() {
651         return mIsMobileDataEnabled;
652     }
653 
654     @VisibleForTesting(visibility = Visibility.PRIVATE)
setMobileDataEnabled(boolean isMobileDataEnabled)655     public void setMobileDataEnabled(boolean isMobileDataEnabled) {
656         mIsMobileDataEnabled = isMobileDataEnabled;
657     }
658 
659     /** Retrieves the network score for a VCN Network */
660     // Package visibility for use in VcnGatewayConnection and VcnNetworkProvider
getNetworkScore()661     static NetworkScore getNetworkScore() {
662         // TODO(b/193687515): Stop setting TRANSPORT_PRIMARY, define a TRANSPORT_VCN, and set in
663         //                    NetworkOffer/NetworkAgent.
664         return new NetworkScore.Builder()
665                 .setLegacyInt(VCN_LEGACY_SCORE_INT)
666                 .setTransportPrimary(true)
667                 .build();
668     }
669 
670     /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
671     @VisibleForTesting(visibility = Visibility.PACKAGE)
672     public interface VcnGatewayStatusCallback {
673         /** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */
onSafeModeStatusChanged()674         void onSafeModeStatusChanged();
675 
676         /** Callback by a VcnGatewayConnection to indicate that an error occurred. */
onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)677         void onGatewayConnectionError(
678                 @NonNull String gatewayConnectionName,
679                 @VcnErrorCode int errorCode,
680                 @Nullable String exceptionClass,
681                 @Nullable String exceptionMessage);
682 
683         /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */
onQuit()684         void onQuit();
685     }
686 
687     private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
688         public final VcnGatewayConnectionConfig mGatewayConnectionConfig;
689 
VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig)690         VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) {
691             mGatewayConnectionConfig = gatewayConnectionConfig;
692         }
693 
694         @Override
onQuit()695         public void onQuit() {
696             sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig));
697         }
698 
699         @Override
onSafeModeStatusChanged()700         public void onSafeModeStatusChanged() {
701             sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED));
702         }
703 
704         @Override
onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)705         public void onGatewayConnectionError(
706                 @NonNull String gatewayConnectionName,
707                 @VcnErrorCode int errorCode,
708                 @Nullable String exceptionClass,
709                 @Nullable String exceptionMessage) {
710             mVcnCallback.onGatewayConnectionError(
711                     gatewayConnectionName, errorCode, exceptionClass, exceptionMessage);
712         }
713     }
714 
715     private class VcnMobileDataContentObserver extends ContentObserver {
VcnMobileDataContentObserver(Handler handler)716         private VcnMobileDataContentObserver(Handler handler) {
717             super(handler);
718         }
719 
720         @Override
onChange(boolean selfChange)721         public void onChange(boolean selfChange) {
722             sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
723         }
724     }
725 
726     @VisibleForTesting(visibility = Visibility.PRIVATE)
727     class VcnUserMobileDataStateListener extends TelephonyCallback
728             implements TelephonyCallback.UserMobileDataStateListener {
729 
730         @Override
onUserMobileDataStateChanged(boolean enabled)731         public void onUserMobileDataStateChanged(boolean enabled) {
732             sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
733         }
734     }
735 
736     /** External dependencies used by Vcn, for injection in tests */
737     @VisibleForTesting(visibility = Visibility.PRIVATE)
738     public static class Dependencies {
739         /** Builds a new VcnGatewayConnection */
newVcnGatewayConnection( VcnContext vcnContext, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, VcnGatewayConnectionConfig connectionConfig, VcnGatewayStatusCallback gatewayStatusCallback, boolean isMobileDataEnabled)740         public VcnGatewayConnection newVcnGatewayConnection(
741                 VcnContext vcnContext,
742                 ParcelUuid subscriptionGroup,
743                 TelephonySubscriptionSnapshot snapshot,
744                 VcnGatewayConnectionConfig connectionConfig,
745                 VcnGatewayStatusCallback gatewayStatusCallback,
746                 boolean isMobileDataEnabled) {
747             return new VcnGatewayConnection(
748                     vcnContext,
749                     subscriptionGroup,
750                     snapshot,
751                     connectionConfig,
752                     gatewayStatusCallback,
753                     isMobileDataEnabled);
754         }
755 
756         /** Builds a new VcnContentResolver instance */
newVcnContentResolver(VcnContext vcnContext)757         public VcnContentResolver newVcnContentResolver(VcnContext vcnContext) {
758             return new VcnContentResolver(vcnContext);
759         }
760     }
761 
762     /** Proxy Implementation of NetworkAgent, used for testing. */
763     @VisibleForTesting(visibility = Visibility.PRIVATE)
764     public static class VcnContentResolver {
765         private final ContentResolver mImpl;
766 
VcnContentResolver(VcnContext vcnContext)767         public VcnContentResolver(VcnContext vcnContext) {
768             mImpl = vcnContext.getContext().getContentResolver();
769         }
770 
771         /** Registers the content observer */
registerContentObserver( @onNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer)772         public void registerContentObserver(
773                 @NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer) {
774             mImpl.registerContentObserver(uri, notifyForDescendants, observer);
775         }
776     }
777 }
778