• 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.routeselection;
18 
19 import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
20 
21 import static com.android.server.VcnManagementService.LOCAL_LOG;
22 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiEntryRssiThreshold;
23 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiExitRssiThreshold;
24 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.net.ConnectivityManager;
29 import android.net.ConnectivityManager.NetworkCallback;
30 import android.net.LinkProperties;
31 import android.net.Network;
32 import android.net.NetworkCapabilities;
33 import android.net.NetworkRequest;
34 import android.net.TelephonyNetworkSpecifier;
35 import android.net.vcn.VcnGatewayConnectionConfig;
36 import android.net.vcn.VcnUnderlyingNetworkTemplate;
37 import android.os.Handler;
38 import android.os.HandlerExecutor;
39 import android.os.ParcelUuid;
40 import android.telephony.TelephonyCallback;
41 import android.telephony.TelephonyManager;
42 import android.util.ArrayMap;
43 import android.util.Slog;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.util.IndentingPrintWriter;
47 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
48 import com.android.server.vcn.VcnContext;
49 import com.android.server.vcn.util.LogUtils;
50 
51 import java.util.ArrayList;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Objects;
55 import java.util.Set;
56 import java.util.TreeSet;
57 
58 /**
59  * Tracks a set of Networks underpinning a VcnGatewayConnection.
60  *
61  * <p>A single UnderlyingNetworkController is built to serve a SINGLE VCN Gateway Connection, and
62  * MUST be torn down with the VcnGatewayConnection in order to ensure underlying networks are
63  * allowed to be reaped.
64  *
65  * @hide
66  */
67 public class UnderlyingNetworkController {
68     @NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName();
69 
70     @NonNull private final VcnContext mVcnContext;
71     @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
72     @NonNull private final ParcelUuid mSubscriptionGroup;
73     @NonNull private final UnderlyingNetworkControllerCallback mCb;
74     @NonNull private final Dependencies mDeps;
75     @NonNull private final Handler mHandler;
76     @NonNull private final ConnectivityManager mConnectivityManager;
77     @NonNull private final TelephonyCallback mActiveDataSubIdListener =
78             new VcnActiveDataSubscriptionIdListener();
79 
80     @NonNull private final List<NetworkCallback> mCellBringupCallbacks = new ArrayList<>();
81     @Nullable private NetworkCallback mWifiBringupCallback;
82     @Nullable private NetworkCallback mWifiEntryRssiThresholdCallback;
83     @Nullable private NetworkCallback mWifiExitRssiThresholdCallback;
84     @Nullable private UnderlyingNetworkListener mRouteSelectionCallback;
85 
86     @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
87     @Nullable private PersistableBundleWrapper mCarrierConfig;
88     private boolean mIsQuitting = false;
89 
90     @Nullable private UnderlyingNetworkRecord mCurrentRecord;
91     @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
92 
UnderlyingNetworkController( @onNull VcnContext vcnContext, @NonNull VcnGatewayConnectionConfig connectionConfig, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull UnderlyingNetworkControllerCallback cb)93     public UnderlyingNetworkController(
94             @NonNull VcnContext vcnContext,
95             @NonNull VcnGatewayConnectionConfig connectionConfig,
96             @NonNull ParcelUuid subscriptionGroup,
97             @NonNull TelephonySubscriptionSnapshot snapshot,
98             @NonNull UnderlyingNetworkControllerCallback cb) {
99         this(vcnContext, connectionConfig, subscriptionGroup, snapshot, cb, new Dependencies());
100     }
101 
UnderlyingNetworkController( @onNull VcnContext vcnContext, @NonNull VcnGatewayConnectionConfig connectionConfig, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull UnderlyingNetworkControllerCallback cb, @NonNull Dependencies deps)102     private UnderlyingNetworkController(
103             @NonNull VcnContext vcnContext,
104             @NonNull VcnGatewayConnectionConfig connectionConfig,
105             @NonNull ParcelUuid subscriptionGroup,
106             @NonNull TelephonySubscriptionSnapshot snapshot,
107             @NonNull UnderlyingNetworkControllerCallback cb,
108             @NonNull Dependencies deps) {
109         mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
110         mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
111         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
112         mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
113         mCb = Objects.requireNonNull(cb, "Missing cb");
114         mDeps = Objects.requireNonNull(deps, "Missing deps");
115 
116         mHandler = new Handler(mVcnContext.getLooper());
117 
118         mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
119         mVcnContext
120                 .getContext()
121                 .getSystemService(TelephonyManager.class)
122                 .registerTelephonyCallback(new HandlerExecutor(mHandler), mActiveDataSubIdListener);
123 
124         mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
125 
126         registerOrUpdateNetworkRequests();
127     }
128 
registerOrUpdateNetworkRequests()129     private void registerOrUpdateNetworkRequests() {
130         NetworkCallback oldRouteSelectionCallback = mRouteSelectionCallback;
131         NetworkCallback oldWifiCallback = mWifiBringupCallback;
132         NetworkCallback oldWifiEntryRssiThresholdCallback = mWifiEntryRssiThresholdCallback;
133         NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback;
134         List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
135         mCellBringupCallbacks.clear();
136 
137         // Register new callbacks. Make-before-break; always register new callbacks before removal
138         // of old callbacks
139         if (!mIsQuitting) {
140             mRouteSelectionCallback = new UnderlyingNetworkListener();
141             mConnectivityManager.registerNetworkCallback(
142                     getRouteSelectionRequest(), mRouteSelectionCallback, mHandler);
143 
144             mWifiEntryRssiThresholdCallback = new NetworkBringupCallback();
145             mConnectivityManager.registerNetworkCallback(
146                     getWifiEntryRssiThresholdNetworkRequest(),
147                     mWifiEntryRssiThresholdCallback,
148                     mHandler);
149 
150             mWifiExitRssiThresholdCallback = new NetworkBringupCallback();
151             mConnectivityManager.registerNetworkCallback(
152                     getWifiExitRssiThresholdNetworkRequest(),
153                     mWifiExitRssiThresholdCallback,
154                     mHandler);
155 
156             mWifiBringupCallback = new NetworkBringupCallback();
157             mConnectivityManager.requestBackgroundNetwork(
158                     getWifiNetworkRequest(), mWifiBringupCallback, mHandler);
159 
160             for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
161                 final NetworkBringupCallback cb = new NetworkBringupCallback();
162                 mCellBringupCallbacks.add(cb);
163 
164                 mConnectivityManager.requestBackgroundNetwork(
165                         getCellNetworkRequestForSubId(subId), cb, mHandler);
166             }
167         } else {
168             mRouteSelectionCallback = null;
169             mWifiBringupCallback = null;
170             mWifiEntryRssiThresholdCallback = null;
171             mWifiExitRssiThresholdCallback = null;
172             // mCellBringupCallbacks already cleared above.
173         }
174 
175         // Unregister old callbacks (as necessary)
176         if (oldRouteSelectionCallback != null) {
177             mConnectivityManager.unregisterNetworkCallback(oldRouteSelectionCallback);
178         }
179         if (oldWifiCallback != null) {
180             mConnectivityManager.unregisterNetworkCallback(oldWifiCallback);
181         }
182         if (oldWifiEntryRssiThresholdCallback != null) {
183             mConnectivityManager.unregisterNetworkCallback(oldWifiEntryRssiThresholdCallback);
184         }
185         if (oldWifiExitRssiThresholdCallback != null) {
186             mConnectivityManager.unregisterNetworkCallback(oldWifiExitRssiThresholdCallback);
187         }
188         for (NetworkCallback cellBringupCallback : oldCellCallbacks) {
189             mConnectivityManager.unregisterNetworkCallback(cellBringupCallback);
190         }
191     }
192 
193     /**
194      * Builds the Route selection request
195      *
196      * <p>This request is guaranteed to select carrier-owned, non-VCN underlying networks by virtue
197      * of a populated set of subIds as expressed in NetworkCapabilities#getSubscriptionIds(). Only
198      * carrier owned networks may be selected, as the request specifies only subIds in the VCN's
199      * subscription group, while the VCN networks are excluded by virtue of not having subIds set on
200      * the VCN-exposed networks.
201      *
202      * <p>If the VCN that this UnderlyingNetworkController belongs to is in test-mode, this will
203      * return a NetworkRequest that only matches Test Networks.
204      */
getRouteSelectionRequest()205     private NetworkRequest getRouteSelectionRequest() {
206         if (mVcnContext.isInTestMode()) {
207             return getTestNetworkRequest(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup));
208         }
209 
210         return getBaseNetworkRequestBuilder()
211                 .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
212                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
213                 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
214                 .build();
215     }
216 
217     /**
218      * Builds the WiFi bringup request
219      *
220      * <p>This request is built specifically to match only carrier-owned WiFi networks, but is also
221      * built to ONLY keep Carrier WiFi Networks alive (but never bring them up). This is a result of
222      * the WifiNetworkFactory not advertising a list of subIds, and therefore not accepting this
223      * request. As such, it will bind to a Carrier WiFi Network that has already been brought up,
224      * but will NEVER bring up a Carrier WiFi network itself.
225      */
getWifiNetworkRequest()226     private NetworkRequest getWifiNetworkRequest() {
227         return getBaseNetworkRequestBuilder()
228                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
229                 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
230                 .build();
231     }
232 
233     /**
234      * Builds the WiFi entry threshold signal strength request
235      *
236      * <p>This request ensures that WiFi reports the crossing of the wifi entry RSSI threshold.
237      * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a
238      * pace to effectively select a short-lived WiFi offload network.
239      */
getWifiEntryRssiThresholdNetworkRequest()240     private NetworkRequest getWifiEntryRssiThresholdNetworkRequest() {
241         return getBaseNetworkRequestBuilder()
242                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
243                 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
244                 // Ensure wifi updates signal strengths when crossing this threshold.
245                 .setSignalStrength(getWifiEntryRssiThreshold(mCarrierConfig))
246                 .build();
247     }
248 
249     /**
250      * Builds the WiFi exit threshold signal strength request
251      *
252      * <p>This request ensures that WiFi reports the crossing of the wifi exit RSSI threshold.
253      * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a
254      * pace to effectively select away from a failing WiFi network.
255      */
getWifiExitRssiThresholdNetworkRequest()256     private NetworkRequest getWifiExitRssiThresholdNetworkRequest() {
257         return getBaseNetworkRequestBuilder()
258                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
259                 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
260                 // Ensure wifi updates signal strengths when crossing this threshold.
261                 .setSignalStrength(getWifiExitRssiThreshold(mCarrierConfig))
262                 .build();
263     }
264 
265     /**
266      * Builds a Cellular bringup request for a given subId
267      *
268      * <p>This request is filed in order to ensure that the Telephony stack always has a
269      * NetworkRequest to bring up a VCN underlying cellular network. It is required in order to
270      * ensure that even when a VCN (appears as Cellular) satisfies the default request, Telephony
271      * will bring up additional underlying Cellular networks.
272      *
273      * <p>Since this request MUST make it to the TelephonyNetworkFactory, subIds are not specified
274      * in the NetworkCapabilities, but rather in the TelephonyNetworkSpecifier.
275      */
getCellNetworkRequestForSubId(int subId)276     private NetworkRequest getCellNetworkRequestForSubId(int subId) {
277         return getBaseNetworkRequestBuilder()
278                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
279                 .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
280                 .build();
281     }
282 
283     /**
284      * Builds and returns a NetworkRequest builder common to all Underlying Network requests
285      */
getBaseNetworkRequestBuilder()286     private NetworkRequest.Builder getBaseNetworkRequestBuilder() {
287         return new NetworkRequest.Builder()
288                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
289                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
290                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
291                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
292     }
293 
294     /** Builds and returns a NetworkRequest for the given subIds to match Test Networks. */
getTestNetworkRequest(@onNull Set<Integer> subIds)295     private NetworkRequest getTestNetworkRequest(@NonNull Set<Integer> subIds) {
296         return new NetworkRequest.Builder()
297                 .clearCapabilities()
298                 .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
299                 .setSubscriptionIds(subIds)
300                 .build();
301     }
302 
303     /**
304      * Update this UnderlyingNetworkController's TelephonySubscriptionSnapshot.
305      *
306      * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkController to
307      * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered
308      * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change.
309      */
updateSubscriptionSnapshot(@onNull TelephonySubscriptionSnapshot newSnapshot)310     public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot newSnapshot) {
311         Objects.requireNonNull(newSnapshot, "Missing newSnapshot");
312 
313         final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot;
314         mLastSnapshot = newSnapshot;
315 
316         // Update carrier config
317         mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
318 
319         // Only trigger re-registration if subIds in this group have changed
320         if (oldSnapshot
321                 .getAllSubIdsInGroup(mSubscriptionGroup)
322                 .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
323             return;
324         }
325         registerOrUpdateNetworkRequests();
326     }
327 
328     /** Tears down this Tracker, and releases all underlying network requests. */
teardown()329     public void teardown() {
330         mVcnContext.ensureRunningOnLooperThread();
331         mIsQuitting = true;
332 
333         // Will unregister all existing callbacks, but not register new ones due to quitting flag.
334         registerOrUpdateNetworkRequests();
335 
336         mVcnContext
337                 .getContext()
338                 .getSystemService(TelephonyManager.class)
339                 .unregisterTelephonyCallback(mActiveDataSubIdListener);
340     }
341 
reevaluateNetworks()342     private void reevaluateNetworks() {
343         if (mIsQuitting || mRouteSelectionCallback == null) {
344             return; // UnderlyingNetworkController has quit.
345         }
346 
347         TreeSet<UnderlyingNetworkRecord> sorted =
348                 mRouteSelectionCallback.getSortedUnderlyingNetworks();
349         UnderlyingNetworkRecord candidate = sorted.isEmpty() ? null : sorted.first();
350         if (Objects.equals(mCurrentRecord, candidate)) {
351             return;
352         }
353 
354         String allNetworkPriorities = "";
355         for (UnderlyingNetworkRecord record : sorted) {
356             if (!allNetworkPriorities.isEmpty()) {
357                 allNetworkPriorities += ", ";
358             }
359             allNetworkPriorities += record.network + ": " + record.getPriorityClass();
360         }
361         logInfo(
362                 "Selected network changed to "
363                         + (candidate == null ? null : candidate.network)
364                         + ", selected from list: "
365                         + allNetworkPriorities);
366         mCurrentRecord = candidate;
367         mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
368     }
369 
370     /**
371      * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped.
372      *
373      * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being
374      * reaped, and no action is taken on any events firing.
375      */
376     @VisibleForTesting
377     class NetworkBringupCallback extends NetworkCallback {}
378 
379     /**
380      * RouteSelectionCallback is used to select the "best" underlying Network.
381      *
382      * <p>The "best" network is determined by ConnectivityService, which is treated as a source of
383      * truth.
384      */
385     @VisibleForTesting
386     class UnderlyingNetworkListener extends NetworkCallback {
387         private final Map<Network, UnderlyingNetworkRecord.Builder>
388                 mUnderlyingNetworkRecordBuilders = new ArrayMap<>();
389 
UnderlyingNetworkListener()390         UnderlyingNetworkListener() {
391             super(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO);
392         }
393 
getSortedUnderlyingNetworks()394         private TreeSet<UnderlyingNetworkRecord> getSortedUnderlyingNetworks() {
395             TreeSet<UnderlyingNetworkRecord> sorted =
396                     new TreeSet<>(
397                             UnderlyingNetworkRecord.getComparator(
398                                     mVcnContext,
399                                     mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
400                                     mSubscriptionGroup,
401                                     mLastSnapshot,
402                                     mCurrentRecord,
403                                     mCarrierConfig));
404 
405             for (UnderlyingNetworkRecord.Builder builder :
406                     mUnderlyingNetworkRecordBuilders.values()) {
407                 if (builder.isValid()) {
408                     sorted.add(builder.build());
409                 }
410             }
411 
412             return sorted;
413         }
414 
415         @Override
onAvailable(@onNull Network network)416         public void onAvailable(@NonNull Network network) {
417             mUnderlyingNetworkRecordBuilders.put(
418                     network, new UnderlyingNetworkRecord.Builder(network));
419         }
420 
421         @Override
onLost(@onNull Network network)422         public void onLost(@NonNull Network network) {
423             mUnderlyingNetworkRecordBuilders.remove(network);
424 
425             reevaluateNetworks();
426         }
427 
428         @Override
onCapabilitiesChanged( @onNull Network network, @NonNull NetworkCapabilities networkCapabilities)429         public void onCapabilitiesChanged(
430                 @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
431             final UnderlyingNetworkRecord.Builder builder =
432                     mUnderlyingNetworkRecordBuilders.get(network);
433             if (builder == null) {
434                 logWtf("Got capabilities change for unknown key: " + network);
435                 return;
436             }
437 
438             builder.setNetworkCapabilities(networkCapabilities);
439             if (builder.isValid()) {
440                 reevaluateNetworks();
441             }
442         }
443 
444         @Override
onLinkPropertiesChanged( @onNull Network network, @NonNull LinkProperties linkProperties)445         public void onLinkPropertiesChanged(
446                 @NonNull Network network, @NonNull LinkProperties linkProperties) {
447             final UnderlyingNetworkRecord.Builder builder =
448                     mUnderlyingNetworkRecordBuilders.get(network);
449             if (builder == null) {
450                 logWtf("Got link properties change for unknown key: " + network);
451                 return;
452             }
453 
454             builder.setLinkProperties(linkProperties);
455             if (builder.isValid()) {
456                 reevaluateNetworks();
457             }
458         }
459 
460         @Override
onBlockedStatusChanged(@onNull Network network, boolean isBlocked)461         public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
462             final UnderlyingNetworkRecord.Builder builder =
463                     mUnderlyingNetworkRecordBuilders.get(network);
464             if (builder == null) {
465                 logWtf("Got blocked status change for unknown key: " + network);
466                 return;
467             }
468 
469             builder.setIsBlocked(isBlocked);
470             if (builder.isValid()) {
471                 reevaluateNetworks();
472             }
473         }
474     }
475 
getLogPrefix()476     private String getLogPrefix() {
477         return "("
478                 + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
479                 + "-"
480                 + mConnectionConfig.getGatewayConnectionName()
481                 + "-"
482                 + System.identityHashCode(this)
483                 + ") ";
484     }
485 
getTagLogPrefix()486     private String getTagLogPrefix() {
487         return "[ " + TAG + " " + getLogPrefix() + "]";
488     }
489 
logInfo(String msg)490     private void logInfo(String msg) {
491         Slog.i(TAG, getLogPrefix() + msg);
492         LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg);
493     }
494 
logInfo(String msg, Throwable tr)495     private void logInfo(String msg, Throwable tr) {
496         Slog.i(TAG, getLogPrefix() + msg, tr);
497         LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg + tr);
498     }
499 
logWtf(String msg)500     private void logWtf(String msg) {
501         Slog.wtf(TAG, msg);
502         LOCAL_LOG.log(TAG + "[WTF ] " + getTagLogPrefix() + msg);
503     }
504 
logWtf(String msg, Throwable tr)505     private void logWtf(String msg, Throwable tr) {
506         Slog.wtf(TAG, msg, tr);
507         LOCAL_LOG.log(TAG + "[WTF ] " + getTagLogPrefix() + msg + tr);
508     }
509 
510     /** Dumps the state of this record for logging and debugging purposes. */
dump(IndentingPrintWriter pw)511     public void dump(IndentingPrintWriter pw) {
512         pw.println("UnderlyingNetworkController:");
513         pw.increaseIndent();
514 
515         pw.println("Carrier WiFi Entry Threshold: " + getWifiEntryRssiThreshold(mCarrierConfig));
516         pw.println("Carrier WiFi Exit Threshold: " + getWifiExitRssiThreshold(mCarrierConfig));
517         pw.println(
518                 "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network));
519 
520         pw.println("VcnUnderlyingNetworkTemplate list:");
521         pw.increaseIndent();
522         int index = 0;
523         for (VcnUnderlyingNetworkTemplate priority :
524                 mConnectionConfig.getVcnUnderlyingNetworkPriorities()) {
525             pw.println("Priority index: " + index);
526             priority.dump(pw);
527             index++;
528         }
529         pw.decreaseIndent();
530         pw.println();
531 
532         pw.println("Underlying networks:");
533         pw.increaseIndent();
534         if (mRouteSelectionCallback != null) {
535             for (UnderlyingNetworkRecord record :
536                     mRouteSelectionCallback.getSortedUnderlyingNetworks()) {
537                 record.dump(
538                         mVcnContext,
539                         pw,
540                         mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
541                         mSubscriptionGroup,
542                         mLastSnapshot,
543                         mCurrentRecord,
544                         mCarrierConfig);
545             }
546         }
547         pw.decreaseIndent();
548         pw.println();
549 
550         pw.decreaseIndent();
551     }
552 
553     private class VcnActiveDataSubscriptionIdListener extends TelephonyCallback
554             implements ActiveDataSubscriptionIdListener {
555         @Override
onActiveDataSubscriptionIdChanged(int subId)556         public void onActiveDataSubscriptionIdChanged(int subId) {
557             reevaluateNetworks();
558         }
559     }
560 
561     /** Callbacks for being notified of the changes in, or to the selected underlying network. */
562     public interface UnderlyingNetworkControllerCallback {
563         /**
564          * Fired when a new underlying network is selected, or properties have changed.
565          *
566          * <p>This callback does NOT signal a mobility event.
567          *
568          * @param underlyingNetworkRecord The details of the new underlying network
569          */
onSelectedUnderlyingNetworkChanged( @ullable UnderlyingNetworkRecord underlyingNetworkRecord)570         void onSelectedUnderlyingNetworkChanged(
571                 @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
572     }
573 
574     private static class Dependencies {}
575 }
576