• 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.TRANSPORT_CELLULAR;
20 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
21 import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
22 
23 import static com.android.server.VcnManagementService.LOCAL_LOG;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.net.ConnectivityManager;
28 import android.net.ConnectivityManager.NetworkCallback;
29 import android.net.LinkProperties;
30 import android.net.Network;
31 import android.net.NetworkCapabilities;
32 import android.net.NetworkRequest;
33 import android.net.TelephonyNetworkSpecifier;
34 import android.net.vcn.VcnManager;
35 import android.os.Handler;
36 import android.os.HandlerExecutor;
37 import android.os.ParcelUuid;
38 import android.os.PersistableBundle;
39 import android.telephony.CarrierConfigManager;
40 import android.telephony.SubscriptionManager;
41 import android.telephony.TelephonyCallback;
42 import android.telephony.TelephonyManager;
43 import android.util.ArrayMap;
44 import android.util.Slog;
45 import android.util.SparseArray;
46 
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.annotations.VisibleForTesting.Visibility;
49 import com.android.internal.util.IndentingPrintWriter;
50 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
51 
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.Comparator;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Objects;
58 import java.util.Set;
59 import java.util.TreeSet;
60 
61 /**
62  * Tracks a set of Networks underpinning a VcnGatewayConnection.
63  *
64  * <p>A single UnderlyingNetworkTracker is built to serve a SINGLE VCN Gateway Connection, and MUST
65  * be torn down with the VcnGatewayConnection in order to ensure underlying networks are allowed to
66  * be reaped.
67  *
68  * @hide
69  */
70 public class UnderlyingNetworkTracker {
71     @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName();
72 
73     /**
74      * Minimum signal strength for a WiFi network to be eligible for switching to
75      *
76      * <p>A network that satisfies this is eligible to become the selected underlying network with
77      * no additional conditions
78      */
79     @VisibleForTesting(visibility = Visibility.PRIVATE)
80     static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70;
81 
82     /**
83      * Minimum signal strength to continue using a WiFi network
84      *
85      * <p>A network that satisfies the conditions may ONLY continue to be used if it is already
86      * selected as the underlying network. A WiFi network satisfying this condition, but NOT the
87      * prospective-network RSSI threshold CANNOT be switched to.
88      */
89     @VisibleForTesting(visibility = Visibility.PRIVATE)
90     static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
91 
92     /** Priority for any cellular network for which the subscription is listed as opportunistic */
93     @VisibleForTesting(visibility = Visibility.PRIVATE)
94     static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0;
95 
96     /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */
97     @VisibleForTesting(visibility = Visibility.PRIVATE)
98     static final int PRIORITY_WIFI_IN_USE = 1;
99 
100     /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */
101     @VisibleForTesting(visibility = Visibility.PRIVATE)
102     static final int PRIORITY_WIFI_PROSPECTIVE = 2;
103 
104     /** Priority for any standard macro cellular network */
105     @VisibleForTesting(visibility = Visibility.PRIVATE)
106     static final int PRIORITY_MACRO_CELLULAR = 3;
107 
108     /** Priority for any other networks (including unvalidated, etc) */
109     @VisibleForTesting(visibility = Visibility.PRIVATE)
110     static final int PRIORITY_ANY = Integer.MAX_VALUE;
111 
112     private static final SparseArray<String> PRIORITY_TO_STRING_MAP = new SparseArray<>();
113 
114     static {
PRIORITY_TO_STRING_MAP.put( PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR")115         PRIORITY_TO_STRING_MAP.put(
116                 PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR");
PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE")117         PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE");
PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE")118         PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE");
PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR")119         PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR");
PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY")120         PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY");
121     }
122 
123     @NonNull private final VcnContext mVcnContext;
124     @NonNull private final ParcelUuid mSubscriptionGroup;
125     @NonNull private final UnderlyingNetworkTrackerCallback mCb;
126     @NonNull private final Dependencies mDeps;
127     @NonNull private final Handler mHandler;
128     @NonNull private final ConnectivityManager mConnectivityManager;
129     @NonNull private final TelephonyCallback mActiveDataSubIdListener =
130             new VcnActiveDataSubscriptionIdListener();
131 
132     @NonNull private final List<NetworkCallback> mCellBringupCallbacks = new ArrayList<>();
133     @Nullable private NetworkCallback mWifiBringupCallback;
134     @Nullable private NetworkCallback mWifiEntryRssiThresholdCallback;
135     @Nullable private NetworkCallback mWifiExitRssiThresholdCallback;
136     @Nullable private UnderlyingNetworkListener mRouteSelectionCallback;
137 
138     @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
139     @Nullable private PersistableBundle mCarrierConfig;
140     private boolean mIsQuitting = false;
141 
142     @Nullable private UnderlyingNetworkRecord mCurrentRecord;
143     @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
144 
UnderlyingNetworkTracker( @onNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull UnderlyingNetworkTrackerCallback cb)145     public UnderlyingNetworkTracker(
146             @NonNull VcnContext vcnContext,
147             @NonNull ParcelUuid subscriptionGroup,
148             @NonNull TelephonySubscriptionSnapshot snapshot,
149             @NonNull UnderlyingNetworkTrackerCallback cb) {
150         this(
151                 vcnContext,
152                 subscriptionGroup,
153                 snapshot,
154                 cb,
155                 new Dependencies());
156     }
157 
UnderlyingNetworkTracker( @onNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull UnderlyingNetworkTrackerCallback cb, @NonNull Dependencies deps)158     private UnderlyingNetworkTracker(
159             @NonNull VcnContext vcnContext,
160             @NonNull ParcelUuid subscriptionGroup,
161             @NonNull TelephonySubscriptionSnapshot snapshot,
162             @NonNull UnderlyingNetworkTrackerCallback cb,
163             @NonNull Dependencies deps) {
164         mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
165         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
166         mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
167         mCb = Objects.requireNonNull(cb, "Missing cb");
168         mDeps = Objects.requireNonNull(deps, "Missing deps");
169 
170         mHandler = new Handler(mVcnContext.getLooper());
171 
172         mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
173         mVcnContext
174                 .getContext()
175                 .getSystemService(TelephonyManager.class)
176                 .registerTelephonyCallback(new HandlerExecutor(mHandler), mActiveDataSubIdListener);
177 
178         // TODO: Listen for changes in carrier config that affect this.
179         for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
180             PersistableBundle config =
181                     mVcnContext
182                             .getContext()
183                             .getSystemService(CarrierConfigManager.class)
184                             .getConfigForSubId(subId);
185 
186             if (config != null) {
187                 mCarrierConfig = config;
188 
189                 // Attempt to use (any) non-opportunistic subscription. If this subscription is
190                 // opportunistic, continue and try to find a non-opportunistic subscription, using
191                 // the opportunistic ones as a last resort.
192                 if (!isOpportunistic(mLastSnapshot, Collections.singleton(subId))) {
193                     break;
194                 }
195             }
196         }
197 
198         registerOrUpdateNetworkRequests();
199     }
200 
registerOrUpdateNetworkRequests()201     private void registerOrUpdateNetworkRequests() {
202         NetworkCallback oldRouteSelectionCallback = mRouteSelectionCallback;
203         NetworkCallback oldWifiCallback = mWifiBringupCallback;
204         NetworkCallback oldWifiEntryRssiThresholdCallback = mWifiEntryRssiThresholdCallback;
205         NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback;
206         List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
207         mCellBringupCallbacks.clear();
208 
209         // Register new callbacks. Make-before-break; always register new callbacks before removal
210         // of old callbacks
211         if (!mIsQuitting) {
212             mRouteSelectionCallback = new UnderlyingNetworkListener();
213             mConnectivityManager.registerNetworkCallback(
214                     getRouteSelectionRequest(), mRouteSelectionCallback, mHandler);
215 
216             mWifiEntryRssiThresholdCallback = new NetworkBringupCallback();
217             mConnectivityManager.registerNetworkCallback(
218                     getWifiEntryRssiThresholdNetworkRequest(),
219                     mWifiEntryRssiThresholdCallback,
220                     mHandler);
221 
222             mWifiExitRssiThresholdCallback = new NetworkBringupCallback();
223             mConnectivityManager.registerNetworkCallback(
224                     getWifiExitRssiThresholdNetworkRequest(),
225                     mWifiExitRssiThresholdCallback,
226                     mHandler);
227 
228             mWifiBringupCallback = new NetworkBringupCallback();
229             mConnectivityManager.requestBackgroundNetwork(
230                     getWifiNetworkRequest(), mWifiBringupCallback, mHandler);
231 
232             for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
233                 final NetworkBringupCallback cb = new NetworkBringupCallback();
234                 mCellBringupCallbacks.add(cb);
235 
236                 mConnectivityManager.requestBackgroundNetwork(
237                         getCellNetworkRequestForSubId(subId), cb, mHandler);
238             }
239         } else {
240             mRouteSelectionCallback = null;
241             mWifiBringupCallback = null;
242             mWifiEntryRssiThresholdCallback = null;
243             mWifiExitRssiThresholdCallback = null;
244             // mCellBringupCallbacks already cleared above.
245         }
246 
247         // Unregister old callbacks (as necessary)
248         if (oldRouteSelectionCallback != null) {
249             mConnectivityManager.unregisterNetworkCallback(oldRouteSelectionCallback);
250         }
251         if (oldWifiCallback != null) {
252             mConnectivityManager.unregisterNetworkCallback(oldWifiCallback);
253         }
254         if (oldWifiEntryRssiThresholdCallback != null) {
255             mConnectivityManager.unregisterNetworkCallback(oldWifiEntryRssiThresholdCallback);
256         }
257         if (oldWifiExitRssiThresholdCallback != null) {
258             mConnectivityManager.unregisterNetworkCallback(oldWifiExitRssiThresholdCallback);
259         }
260         for (NetworkCallback cellBringupCallback : oldCellCallbacks) {
261             mConnectivityManager.unregisterNetworkCallback(cellBringupCallback);
262         }
263     }
264 
265     /**
266      * Builds the Route selection request
267      *
268      * <p>This request is guaranteed to select carrier-owned, non-VCN underlying networks by virtue
269      * of a populated set of subIds as expressed in NetworkCapabilities#getSubscriptionIds(). Only
270      * carrier owned networks may be selected, as the request specifies only subIds in the VCN's
271      * subscription group, while the VCN networks are excluded by virtue of not having subIds set on
272      * the VCN-exposed networks.
273      *
274      * <p>If the VCN that this UnderlyingNetworkTracker belongs to is in test-mode, this will return
275      * a NetworkRequest that only matches Test Networks.
276      */
getRouteSelectionRequest()277     private NetworkRequest getRouteSelectionRequest() {
278         if (mVcnContext.isInTestMode()) {
279             return getTestNetworkRequest(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup));
280         }
281 
282         return getBaseNetworkRequestBuilder()
283                 .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
284                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
285                 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
286                 .build();
287     }
288 
289     /**
290      * Builds the WiFi bringup request
291      *
292      * <p>This request is built specifically to match only carrier-owned WiFi networks, but is also
293      * built to ONLY keep Carrier WiFi Networks alive (but never bring them up). This is a result of
294      * the WifiNetworkFactory not advertising a list of subIds, and therefore not accepting this
295      * request. As such, it will bind to a Carrier WiFi Network that has already been brought up,
296      * but will NEVER bring up a Carrier WiFi network itself.
297      */
getWifiNetworkRequest()298     private NetworkRequest getWifiNetworkRequest() {
299         return getBaseNetworkRequestBuilder()
300                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
301                 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
302                 .build();
303     }
304 
305     /**
306      * Builds the WiFi entry threshold signal strength request
307      *
308      * <p>This request ensures that WiFi reports the crossing of the wifi entry RSSI threshold.
309      * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a
310      * pace to effectively select a short-lived WiFi offload network.
311      */
getWifiEntryRssiThresholdNetworkRequest()312     private NetworkRequest getWifiEntryRssiThresholdNetworkRequest() {
313         return getBaseNetworkRequestBuilder()
314                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
315                 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
316                 // Ensure wifi updates signal strengths when crossing this threshold.
317                 .setSignalStrength(getWifiEntryRssiThreshold(mCarrierConfig))
318                 .build();
319     }
320 
321     /**
322      * Builds the WiFi exit threshold signal strength request
323      *
324      * <p>This request ensures that WiFi reports the crossing of the wifi exit RSSI threshold.
325      * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a
326      * pace to effectively select away from a failing WiFi network.
327      */
getWifiExitRssiThresholdNetworkRequest()328     private NetworkRequest getWifiExitRssiThresholdNetworkRequest() {
329         return getBaseNetworkRequestBuilder()
330                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
331                 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
332                 // Ensure wifi updates signal strengths when crossing this threshold.
333                 .setSignalStrength(getWifiExitRssiThreshold(mCarrierConfig))
334                 .build();
335     }
336 
337     /**
338      * Builds a Cellular bringup request for a given subId
339      *
340      * <p>This request is filed in order to ensure that the Telephony stack always has a
341      * NetworkRequest to bring up a VCN underlying cellular network. It is required in order to
342      * ensure that even when a VCN (appears as Cellular) satisfies the default request, Telephony
343      * will bring up additional underlying Cellular networks.
344      *
345      * <p>Since this request MUST make it to the TelephonyNetworkFactory, subIds are not specified
346      * in the NetworkCapabilities, but rather in the TelephonyNetworkSpecifier.
347      */
getCellNetworkRequestForSubId(int subId)348     private NetworkRequest getCellNetworkRequestForSubId(int subId) {
349         return getBaseNetworkRequestBuilder()
350                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
351                 .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
352                 .build();
353     }
354 
355     /**
356      * Builds and returns a NetworkRequest builder common to all Underlying Network requests
357      */
getBaseNetworkRequestBuilder()358     private NetworkRequest.Builder getBaseNetworkRequestBuilder() {
359         return new NetworkRequest.Builder()
360                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
361                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
362                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
363                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
364     }
365 
366     /** Builds and returns a NetworkRequest for the given subIds to match Test Networks. */
getTestNetworkRequest(@onNull Set<Integer> subIds)367     private NetworkRequest getTestNetworkRequest(@NonNull Set<Integer> subIds) {
368         return new NetworkRequest.Builder()
369                 .clearCapabilities()
370                 .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
371                 .setSubscriptionIds(subIds)
372                 .build();
373     }
374 
375     /**
376      * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot.
377      *
378      * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkTracker to
379      * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered
380      * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change.
381      */
updateSubscriptionSnapshot(@onNull TelephonySubscriptionSnapshot newSnapshot)382     public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot newSnapshot) {
383         Objects.requireNonNull(newSnapshot, "Missing newSnapshot");
384 
385         final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot;
386         mLastSnapshot = newSnapshot;
387 
388         // Only trigger re-registration if subIds in this group have changed
389         if (oldSnapshot
390                 .getAllSubIdsInGroup(mSubscriptionGroup)
391                 .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
392             return;
393         }
394         registerOrUpdateNetworkRequests();
395     }
396 
397     /** Tears down this Tracker, and releases all underlying network requests. */
teardown()398     public void teardown() {
399         mVcnContext.ensureRunningOnLooperThread();
400         mIsQuitting = true;
401 
402         // Will unregister all existing callbacks, but not register new ones due to quitting flag.
403         registerOrUpdateNetworkRequests();
404 
405         mVcnContext
406                 .getContext()
407                 .getSystemService(TelephonyManager.class)
408                 .unregisterTelephonyCallback(mActiveDataSubIdListener);
409     }
410 
reevaluateNetworks()411     private void reevaluateNetworks() {
412         if (mIsQuitting || mRouteSelectionCallback == null) {
413             return; // UnderlyingNetworkTracker has quit.
414         }
415 
416         TreeSet<UnderlyingNetworkRecord> sorted =
417                 mRouteSelectionCallback.getSortedUnderlyingNetworks();
418         UnderlyingNetworkRecord candidate = sorted.isEmpty() ? null : sorted.first();
419         if (Objects.equals(mCurrentRecord, candidate)) {
420             return;
421         }
422 
423         mCurrentRecord = candidate;
424         mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
425     }
426 
isOpportunistic( @onNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds)427     private static boolean isOpportunistic(
428             @NonNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds) {
429         if (snapshot == null) {
430             logWtf("Got null snapshot");
431             return false;
432         }
433 
434         for (int subId : subIds) {
435             if (snapshot.isOpportunistic(subId)) {
436                 return true;
437             }
438         }
439 
440         return false;
441     }
442 
443     /**
444      * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped.
445      *
446      * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being
447      * reaped, and no action is taken on any events firing.
448      */
449     @VisibleForTesting
450     class NetworkBringupCallback extends NetworkCallback {}
451 
452     /**
453      * RouteSelectionCallback is used to select the "best" underlying Network.
454      *
455      * <p>The "best" network is determined by ConnectivityService, which is treated as a source of
456      * truth.
457      */
458     @VisibleForTesting
459     class UnderlyingNetworkListener extends NetworkCallback {
460         private final Map<Network, UnderlyingNetworkRecord.Builder>
461                 mUnderlyingNetworkRecordBuilders = new ArrayMap<>();
462 
UnderlyingNetworkListener()463         UnderlyingNetworkListener() {
464             super(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO);
465         }
466 
getSortedUnderlyingNetworks()467         private TreeSet<UnderlyingNetworkRecord> getSortedUnderlyingNetworks() {
468             TreeSet<UnderlyingNetworkRecord> sorted =
469                     new TreeSet<>(
470                             UnderlyingNetworkRecord.getComparator(
471                                     mSubscriptionGroup,
472                                     mLastSnapshot,
473                                     mCurrentRecord,
474                                     mCarrierConfig));
475 
476             for (UnderlyingNetworkRecord.Builder builder :
477                     mUnderlyingNetworkRecordBuilders.values()) {
478                 if (builder.isValid()) {
479                     sorted.add(builder.build());
480                 }
481             }
482 
483             return sorted;
484         }
485 
486         @Override
onAvailable(@onNull Network network)487         public void onAvailable(@NonNull Network network) {
488             mUnderlyingNetworkRecordBuilders.put(
489                     network, new UnderlyingNetworkRecord.Builder(network));
490         }
491 
492         @Override
onLost(@onNull Network network)493         public void onLost(@NonNull Network network) {
494             mUnderlyingNetworkRecordBuilders.remove(network);
495 
496             reevaluateNetworks();
497         }
498 
499         @Override
onCapabilitiesChanged( @onNull Network network, @NonNull NetworkCapabilities networkCapabilities)500         public void onCapabilitiesChanged(
501                 @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
502             final UnderlyingNetworkRecord.Builder builder =
503                     mUnderlyingNetworkRecordBuilders.get(network);
504             if (builder == null) {
505                 logWtf("Got capabilities change for unknown key: " + network);
506                 return;
507             }
508 
509             builder.setNetworkCapabilities(networkCapabilities);
510             if (builder.isValid()) {
511                 reevaluateNetworks();
512             }
513         }
514 
515         @Override
onLinkPropertiesChanged( @onNull Network network, @NonNull LinkProperties linkProperties)516         public void onLinkPropertiesChanged(
517                 @NonNull Network network, @NonNull LinkProperties linkProperties) {
518             final UnderlyingNetworkRecord.Builder builder =
519                     mUnderlyingNetworkRecordBuilders.get(network);
520             if (builder == null) {
521                 logWtf("Got link properties change for unknown key: " + network);
522                 return;
523             }
524 
525             builder.setLinkProperties(linkProperties);
526             if (builder.isValid()) {
527                 reevaluateNetworks();
528             }
529         }
530 
531         @Override
onBlockedStatusChanged(@onNull Network network, boolean isBlocked)532         public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
533             final UnderlyingNetworkRecord.Builder builder =
534                     mUnderlyingNetworkRecordBuilders.get(network);
535             if (builder == null) {
536                 logWtf("Got blocked status change for unknown key: " + network);
537                 return;
538             }
539 
540             builder.setIsBlocked(isBlocked);
541             if (builder.isValid()) {
542                 reevaluateNetworks();
543             }
544         }
545     }
546 
getWifiEntryRssiThreshold(@ullable PersistableBundle carrierConfig)547     private static int getWifiEntryRssiThreshold(@Nullable PersistableBundle carrierConfig) {
548         if (carrierConfig != null) {
549             return carrierConfig.getInt(
550                     VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
551                     WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT);
552         }
553 
554         return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
555     }
556 
getWifiExitRssiThreshold(@ullable PersistableBundle carrierConfig)557     private static int getWifiExitRssiThreshold(@Nullable PersistableBundle carrierConfig) {
558         if (carrierConfig != null) {
559             return carrierConfig.getInt(
560                     VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
561                     WIFI_EXIT_RSSI_THRESHOLD_DEFAULT);
562         }
563 
564         return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
565     }
566 
567     /** A record of a single underlying network, caching relevant fields. */
568     public static class UnderlyingNetworkRecord {
569         @NonNull public final Network network;
570         @NonNull public final NetworkCapabilities networkCapabilities;
571         @NonNull public final LinkProperties linkProperties;
572         public final boolean isBlocked;
573 
574         @VisibleForTesting(visibility = Visibility.PRIVATE)
UnderlyingNetworkRecord( @onNull Network network, @NonNull NetworkCapabilities networkCapabilities, @NonNull LinkProperties linkProperties, boolean isBlocked)575         UnderlyingNetworkRecord(
576                 @NonNull Network network,
577                 @NonNull NetworkCapabilities networkCapabilities,
578                 @NonNull LinkProperties linkProperties,
579                 boolean isBlocked) {
580             this.network = network;
581             this.networkCapabilities = networkCapabilities;
582             this.linkProperties = linkProperties;
583             this.isBlocked = isBlocked;
584         }
585 
586         @Override
equals(Object o)587         public boolean equals(Object o) {
588             if (this == o) return true;
589             if (!(o instanceof UnderlyingNetworkRecord)) return false;
590             final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o;
591 
592             return network.equals(that.network)
593                     && networkCapabilities.equals(that.networkCapabilities)
594                     && linkProperties.equals(that.linkProperties)
595                     && isBlocked == that.isBlocked;
596         }
597 
598         @Override
hashCode()599         public int hashCode() {
600             return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
601         }
602 
603         /**
604          * Gives networks a priority class, based on the following priorities:
605          *
606          * <ol>
607          *   <li>Opportunistic cellular
608          *   <li>Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT
609          *   <li>Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT
610          *   <li>Macro cellular
611          *   <li>Any others
612          * </ol>
613          */
calculatePriorityClass( ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig)614         private int calculatePriorityClass(
615                 ParcelUuid subscriptionGroup,
616                 TelephonySubscriptionSnapshot snapshot,
617                 UnderlyingNetworkRecord currentlySelected,
618                 PersistableBundle carrierConfig) {
619             final NetworkCapabilities caps = networkCapabilities;
620 
621             // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
622 
623             if (isBlocked) {
624                 logWtf("Network blocked for System Server: " + network);
625                 return PRIORITY_ANY;
626             }
627 
628             if (caps.hasTransport(TRANSPORT_CELLULAR)
629                     && isOpportunistic(snapshot, caps.getSubscriptionIds())) {
630                 // If this carrier is the active data provider, ensure that opportunistic is only
631                 // ever prioritized if it is also the active data subscription. This ensures that
632                 // if an opportunistic subscription is still in the process of being switched to,
633                 // or switched away from, the VCN does not attempt to continue using it against the
634                 // decision made at the telephony layer. Failure to do so may result in the modem
635                 // switching back and forth.
636                 //
637                 // Allow the following two cases:
638                 // 1. Active subId is NOT in the group that this VCN is supporting
639                 // 2. This opportunistic subscription is for the active subId
640                 if (!snapshot.getAllSubIdsInGroup(subscriptionGroup)
641                                 .contains(SubscriptionManager.getActiveDataSubscriptionId())
642                         || caps.getSubscriptionIds()
643                                 .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
644                     return PRIORITY_OPPORTUNISTIC_CELLULAR;
645                 }
646             }
647 
648             if (caps.hasTransport(TRANSPORT_WIFI)) {
649                 if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)
650                         && currentlySelected != null
651                         && network.equals(currentlySelected.network)) {
652                     return PRIORITY_WIFI_IN_USE;
653                 }
654 
655                 if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
656                     return PRIORITY_WIFI_PROSPECTIVE;
657                 }
658             }
659 
660             // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might
661             // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be
662             // the case if the Default Data SubId does not support certain services (eg voice
663             // calling)
664             if (caps.hasTransport(TRANSPORT_CELLULAR)
665                     && !isOpportunistic(snapshot, caps.getSubscriptionIds())) {
666                 return PRIORITY_MACRO_CELLULAR;
667             }
668 
669             return PRIORITY_ANY;
670         }
671 
getComparator( ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig)672         private static Comparator<UnderlyingNetworkRecord> getComparator(
673                 ParcelUuid subscriptionGroup,
674                 TelephonySubscriptionSnapshot snapshot,
675                 UnderlyingNetworkRecord currentlySelected,
676                 PersistableBundle carrierConfig) {
677             return (left, right) -> {
678                 return Integer.compare(
679                         left.calculatePriorityClass(
680                                 subscriptionGroup, snapshot, currentlySelected, carrierConfig),
681                         right.calculatePriorityClass(
682                                 subscriptionGroup, snapshot, currentlySelected, carrierConfig));
683             };
684         }
685 
686         /** Dumps the state of this record for logging and debugging purposes. */
dump( IndentingPrintWriter pw, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig)687         private void dump(
688                 IndentingPrintWriter pw,
689                 ParcelUuid subscriptionGroup,
690                 TelephonySubscriptionSnapshot snapshot,
691                 UnderlyingNetworkRecord currentlySelected,
692                 PersistableBundle carrierConfig) {
693             pw.println("UnderlyingNetworkRecord:");
694             pw.increaseIndent();
695 
696             final int priorityClass =
697                     calculatePriorityClass(
698                             subscriptionGroup, snapshot, currentlySelected, carrierConfig);
699             pw.println(
700                     "Priority class: " + PRIORITY_TO_STRING_MAP.get(priorityClass) + " ("
701                             + priorityClass + ")");
702             pw.println("mNetwork: " + network);
703             pw.println("mNetworkCapabilities: " + networkCapabilities);
704             pw.println("mLinkProperties: " + linkProperties);
705 
706             pw.decreaseIndent();
707         }
708 
709         /** Builder to incrementally construct an UnderlyingNetworkRecord. */
710         private static class Builder {
711             @NonNull private final Network mNetwork;
712 
713             @Nullable private NetworkCapabilities mNetworkCapabilities;
714             @Nullable private LinkProperties mLinkProperties;
715             boolean mIsBlocked;
716             boolean mWasIsBlockedSet;
717 
718             @Nullable private UnderlyingNetworkRecord mCached;
719 
Builder(@onNull Network network)720             private Builder(@NonNull Network network) {
721                 mNetwork = network;
722             }
723 
724             @NonNull
getNetwork()725             private Network getNetwork() {
726                 return mNetwork;
727             }
728 
setNetworkCapabilities(@onNull NetworkCapabilities networkCapabilities)729             private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
730                 mNetworkCapabilities = networkCapabilities;
731                 mCached = null;
732             }
733 
734             @Nullable
getNetworkCapabilities()735             private NetworkCapabilities getNetworkCapabilities() {
736                 return mNetworkCapabilities;
737             }
738 
setLinkProperties(@onNull LinkProperties linkProperties)739             private void setLinkProperties(@NonNull LinkProperties linkProperties) {
740                 mLinkProperties = linkProperties;
741                 mCached = null;
742             }
743 
setIsBlocked(boolean isBlocked)744             private void setIsBlocked(boolean isBlocked) {
745                 mIsBlocked = isBlocked;
746                 mWasIsBlockedSet = true;
747                 mCached = null;
748             }
749 
isValid()750             private boolean isValid() {
751                 return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
752             }
753 
build()754             private UnderlyingNetworkRecord build() {
755                 if (!isValid()) {
756                     throw new IllegalArgumentException(
757                             "Called build before UnderlyingNetworkRecord was valid");
758                 }
759 
760                 if (mCached == null) {
761                     mCached =
762                             new UnderlyingNetworkRecord(
763                                     mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
764                 }
765 
766                 return mCached;
767             }
768         }
769     }
770 
logWtf(String msg)771     private static void logWtf(String msg) {
772         Slog.wtf(TAG, msg);
773         LOCAL_LOG.log(TAG + " WTF: " + msg);
774     }
775 
logWtf(String msg, Throwable tr)776     private static void logWtf(String msg, Throwable tr) {
777         Slog.wtf(TAG, msg, tr);
778         LOCAL_LOG.log(TAG + " WTF: " + msg + tr);
779     }
780 
781     /** Dumps the state of this record for logging and debugging purposes. */
dump(IndentingPrintWriter pw)782     public void dump(IndentingPrintWriter pw) {
783         pw.println("UnderlyingNetworkTracker:");
784         pw.increaseIndent();
785 
786         pw.println("Carrier WiFi Entry Threshold: " + getWifiEntryRssiThreshold(mCarrierConfig));
787         pw.println("Carrier WiFi Exit Threshold: " + getWifiExitRssiThreshold(mCarrierConfig));
788         pw.println(
789                 "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network));
790 
791         pw.println("Underlying networks:");
792         pw.increaseIndent();
793         if (mRouteSelectionCallback != null) {
794             for (UnderlyingNetworkRecord record :
795                     mRouteSelectionCallback.getSortedUnderlyingNetworks()) {
796                 record.dump(pw, mSubscriptionGroup, mLastSnapshot, mCurrentRecord, mCarrierConfig);
797             }
798         }
799         pw.decreaseIndent();
800         pw.println();
801 
802         pw.decreaseIndent();
803     }
804 
805     private class VcnActiveDataSubscriptionIdListener extends TelephonyCallback
806             implements ActiveDataSubscriptionIdListener {
807         @Override
onActiveDataSubscriptionIdChanged(int subId)808         public void onActiveDataSubscriptionIdChanged(int subId) {
809             reevaluateNetworks();
810         }
811     }
812 
813     /** Callbacks for being notified of the changes in, or to the selected underlying network. */
814     public interface UnderlyingNetworkTrackerCallback {
815         /**
816          * Fired when a new underlying network is selected, or properties have changed.
817          *
818          * <p>This callback does NOT signal a mobility event.
819          *
820          * @param underlyingNetworkRecord The details of the new underlying network
821          */
onSelectedUnderlyingNetworkChanged( @ullable UnderlyingNetworkRecord underlyingNetworkRecord)822         void onSelectedUnderlyingNetworkChanged(
823                 @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
824     }
825 
826     private static class Dependencies {}
827 }
828