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