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