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      *
630      * <p>This method is not thread safe and MUST run on the VCN thread.
631      */
dump(IndentingPrintWriter pw)632     public void dump(IndentingPrintWriter pw) {
633         mVcnContext.ensureRunningOnLooperThread();
634 
635         pw.println("Vcn (" + mSubscriptionGroup + "):");
636         pw.increaseIndent();
637 
638         pw.println("mCurrentStatus: " + mCurrentStatus);
639         pw.println("mIsMobileDataEnabled: " + mIsMobileDataEnabled);
640         pw.println();
641 
642         pw.println("mVcnGatewayConnections:");
643         pw.increaseIndent();
644         for (VcnGatewayConnection gw : mVcnGatewayConnections.values()) {
645             gw.dump(pw);
646         }
647         pw.decreaseIndent();
648         pw.println();
649 
650         pw.decreaseIndent();
651     }
652 
653     @VisibleForTesting(visibility = Visibility.PRIVATE)
isMobileDataEnabled()654     public boolean isMobileDataEnabled() {
655         return mIsMobileDataEnabled;
656     }
657 
658     @VisibleForTesting(visibility = Visibility.PRIVATE)
setMobileDataEnabled(boolean isMobileDataEnabled)659     public void setMobileDataEnabled(boolean isMobileDataEnabled) {
660         mIsMobileDataEnabled = isMobileDataEnabled;
661     }
662 
663     /** Retrieves the network score for a VCN Network */
664     // Package visibility for use in VcnGatewayConnection and VcnNetworkProvider
getNetworkScore()665     static NetworkScore getNetworkScore() {
666         // TODO(b/193687515): Stop setting TRANSPORT_PRIMARY, define a TRANSPORT_VCN, and set in
667         //                    NetworkOffer/NetworkAgent.
668         return new NetworkScore.Builder()
669                 .setLegacyInt(VCN_LEGACY_SCORE_INT)
670                 .setTransportPrimary(true)
671                 .build();
672     }
673 
674     /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
675     @VisibleForTesting(visibility = Visibility.PACKAGE)
676     public interface VcnGatewayStatusCallback {
677         /** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */
onSafeModeStatusChanged()678         void onSafeModeStatusChanged();
679 
680         /** Callback by a VcnGatewayConnection to indicate that an error occurred. */
onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)681         void onGatewayConnectionError(
682                 @NonNull String gatewayConnectionName,
683                 @VcnErrorCode int errorCode,
684                 @Nullable String exceptionClass,
685                 @Nullable String exceptionMessage);
686 
687         /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */
onQuit()688         void onQuit();
689     }
690 
691     private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
692         public final VcnGatewayConnectionConfig mGatewayConnectionConfig;
693 
VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig)694         VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) {
695             mGatewayConnectionConfig = gatewayConnectionConfig;
696         }
697 
698         @Override
onQuit()699         public void onQuit() {
700             sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig));
701         }
702 
703         @Override
onSafeModeStatusChanged()704         public void onSafeModeStatusChanged() {
705             sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED));
706         }
707 
708         @Override
onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)709         public void onGatewayConnectionError(
710                 @NonNull String gatewayConnectionName,
711                 @VcnErrorCode int errorCode,
712                 @Nullable String exceptionClass,
713                 @Nullable String exceptionMessage) {
714             mVcnCallback.onGatewayConnectionError(
715                     gatewayConnectionName, errorCode, exceptionClass, exceptionMessage);
716         }
717     }
718 
719     private class VcnMobileDataContentObserver extends ContentObserver {
VcnMobileDataContentObserver(Handler handler)720         private VcnMobileDataContentObserver(Handler handler) {
721             super(handler);
722         }
723 
724         @Override
onChange(boolean selfChange)725         public void onChange(boolean selfChange) {
726             sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
727         }
728     }
729 
730     @VisibleForTesting(visibility = Visibility.PRIVATE)
731     class VcnUserMobileDataStateListener extends TelephonyCallback
732             implements TelephonyCallback.UserMobileDataStateListener {
733 
734         @Override
onUserMobileDataStateChanged(boolean enabled)735         public void onUserMobileDataStateChanged(boolean enabled) {
736             sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
737         }
738     }
739 
740     /** External dependencies used by Vcn, for injection in tests */
741     @VisibleForTesting(visibility = Visibility.PRIVATE)
742     public static class Dependencies {
743         /** Builds a new VcnGatewayConnection */
newVcnGatewayConnection( VcnContext vcnContext, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, VcnGatewayConnectionConfig connectionConfig, VcnGatewayStatusCallback gatewayStatusCallback, boolean isMobileDataEnabled)744         public VcnGatewayConnection newVcnGatewayConnection(
745                 VcnContext vcnContext,
746                 ParcelUuid subscriptionGroup,
747                 TelephonySubscriptionSnapshot snapshot,
748                 VcnGatewayConnectionConfig connectionConfig,
749                 VcnGatewayStatusCallback gatewayStatusCallback,
750                 boolean isMobileDataEnabled) {
751             return new VcnGatewayConnection(
752                     vcnContext,
753                     subscriptionGroup,
754                     snapshot,
755                     connectionConfig,
756                     gatewayStatusCallback,
757                     isMobileDataEnabled);
758         }
759 
760         /** Builds a new VcnContentResolver instance */
newVcnContentResolver(VcnContext vcnContext)761         public VcnContentResolver newVcnContentResolver(VcnContext vcnContext) {
762             return new VcnContentResolver(vcnContext);
763         }
764     }
765 
766     /** Proxy Implementation of NetworkAgent, used for testing. */
767     @VisibleForTesting(visibility = Visibility.PRIVATE)
768     public static class VcnContentResolver {
769         private final ContentResolver mImpl;
770 
VcnContentResolver(VcnContext vcnContext)771         public VcnContentResolver(VcnContext vcnContext) {
772             mImpl = vcnContext.getContext().getContentResolver();
773         }
774 
775         /** Registers the content observer */
registerContentObserver( @onNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer)776         public void registerContentObserver(
777                 @NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer) {
778             mImpl.registerContentObserver(uri, notifyForDescendants, observer);
779         }
780     }
781 }
782