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