• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package com.android.server.vcn.routeselection;
17 
18 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
20 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
21 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
22 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
23 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
24 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
25 
26 import static com.android.server.VcnManagementService.LOCAL_LOG;
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.NetworkCapabilities;
32 import android.net.TelephonyNetworkSpecifier;
33 import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
34 import android.net.vcn.VcnManager;
35 import android.net.vcn.VcnUnderlyingNetworkTemplate;
36 import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
37 import android.os.ParcelUuid;
38 import android.telephony.SubscriptionManager;
39 import android.telephony.TelephonyManager;
40 import android.util.Slog;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.annotations.VisibleForTesting.Visibility;
44 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
45 import com.android.server.vcn.VcnContext;
46 
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.Set;
50 
51 /** @hide */
52 class NetworkPriorityClassifier {
53     @NonNull private static final String TAG = NetworkPriorityClassifier.class.getSimpleName();
54     /**
55      * Minimum signal strength for a WiFi network to be eligible for switching to
56      *
57      * <p>A network that satisfies this is eligible to become the selected underlying network with
58      * no additional conditions
59      */
60     @VisibleForTesting(visibility = Visibility.PRIVATE)
61     static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70;
62     /**
63      * Minimum signal strength to continue using a WiFi network
64      *
65      * <p>A network that satisfies the conditions may ONLY continue to be used if it is already
66      * selected as the underlying network. A WiFi network satisfying this condition, but NOT the
67      * prospective-network RSSI threshold CANNOT be switched to.
68      */
69     @VisibleForTesting(visibility = Visibility.PRIVATE)
70     static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
71 
72     /** Priority for any other networks (including unvalidated, etc) */
73     @VisibleForTesting(visibility = Visibility.PRIVATE)
74     static final int PRIORITY_ANY = Integer.MAX_VALUE;
75 
76     /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
calculatePriorityClass( VcnContext vcnContext, UnderlyingNetworkRecord networkRecord, List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundleWrapper carrierConfig)77     public static int calculatePriorityClass(
78             VcnContext vcnContext,
79             UnderlyingNetworkRecord networkRecord,
80             List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
81             ParcelUuid subscriptionGroup,
82             TelephonySubscriptionSnapshot snapshot,
83             UnderlyingNetworkRecord currentlySelected,
84             PersistableBundleWrapper carrierConfig) {
85         // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
86 
87         if (networkRecord.isBlocked) {
88             logWtf("Network blocked for System Server: " + networkRecord.network);
89             return PRIORITY_ANY;
90         }
91 
92         if (snapshot == null) {
93             logWtf("Got null snapshot");
94             return PRIORITY_ANY;
95         }
96 
97         int priorityIndex = 0;
98         for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkTemplates) {
99             if (checkMatchesPriorityRule(
100                     vcnContext,
101                     nwPriority,
102                     networkRecord,
103                     subscriptionGroup,
104                     snapshot,
105                     currentlySelected,
106                     carrierConfig)) {
107                 return priorityIndex;
108             }
109             priorityIndex++;
110         }
111         return PRIORITY_ANY;
112     }
113 
114     @VisibleForTesting(visibility = Visibility.PRIVATE)
checkMatchesPriorityRule( VcnContext vcnContext, VcnUnderlyingNetworkTemplate networkPriority, UnderlyingNetworkRecord networkRecord, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundleWrapper carrierConfig)115     public static boolean checkMatchesPriorityRule(
116             VcnContext vcnContext,
117             VcnUnderlyingNetworkTemplate networkPriority,
118             UnderlyingNetworkRecord networkRecord,
119             ParcelUuid subscriptionGroup,
120             TelephonySubscriptionSnapshot snapshot,
121             UnderlyingNetworkRecord currentlySelected,
122             PersistableBundleWrapper carrierConfig) {
123         final NetworkCapabilities caps = networkRecord.networkCapabilities;
124         final boolean isSelectedUnderlyingNetwork =
125                 currentlySelected != null
126                         && Objects.equals(currentlySelected.network, networkRecord.network);
127 
128         final int meteredMatch = networkPriority.getMetered();
129         final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
130         if (meteredMatch == MATCH_REQUIRED && !isMetered
131                 || meteredMatch == MATCH_FORBIDDEN && isMetered) {
132             return false;
133         }
134 
135         // Fails bandwidth requirements if either (a) less than exit threshold, or (b), not
136         // selected, but less than entry threshold
137         if (caps.getLinkUpstreamBandwidthKbps() < networkPriority.getMinExitUpstreamBandwidthKbps()
138                 || (caps.getLinkUpstreamBandwidthKbps()
139                                 < networkPriority.getMinEntryUpstreamBandwidthKbps()
140                         && !isSelectedUnderlyingNetwork)) {
141             return false;
142         }
143 
144         if (caps.getLinkDownstreamBandwidthKbps()
145                         < networkPriority.getMinExitDownstreamBandwidthKbps()
146                 || (caps.getLinkDownstreamBandwidthKbps()
147                                 < networkPriority.getMinEntryDownstreamBandwidthKbps()
148                         && !isSelectedUnderlyingNetwork)) {
149             return false;
150         }
151 
152         if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
153             return true;
154         }
155 
156         if (networkPriority instanceof VcnWifiUnderlyingNetworkTemplate) {
157             return checkMatchesWifiPriorityRule(
158                     (VcnWifiUnderlyingNetworkTemplate) networkPriority,
159                     networkRecord,
160                     currentlySelected,
161                     carrierConfig);
162         }
163 
164         if (networkPriority instanceof VcnCellUnderlyingNetworkTemplate) {
165             return checkMatchesCellPriorityRule(
166                     vcnContext,
167                     (VcnCellUnderlyingNetworkTemplate) networkPriority,
168                     networkRecord,
169                     subscriptionGroup,
170                     snapshot);
171         }
172 
173         logWtf(
174                 "Got unknown VcnUnderlyingNetworkTemplate class: "
175                         + networkPriority.getClass().getSimpleName());
176         return false;
177     }
178 
179     @VisibleForTesting(visibility = Visibility.PRIVATE)
checkMatchesWifiPriorityRule( VcnWifiUnderlyingNetworkTemplate networkPriority, UnderlyingNetworkRecord networkRecord, UnderlyingNetworkRecord currentlySelected, PersistableBundleWrapper carrierConfig)180     public static boolean checkMatchesWifiPriorityRule(
181             VcnWifiUnderlyingNetworkTemplate networkPriority,
182             UnderlyingNetworkRecord networkRecord,
183             UnderlyingNetworkRecord currentlySelected,
184             PersistableBundleWrapper carrierConfig) {
185         final NetworkCapabilities caps = networkRecord.networkCapabilities;
186 
187         if (!caps.hasTransport(TRANSPORT_WIFI)) {
188             return false;
189         }
190 
191         // TODO: Move the Network Quality check to the network metric monitor framework.
192         if (!isWifiRssiAcceptable(networkRecord, currentlySelected, carrierConfig)) {
193             return false;
194         }
195 
196         if (!networkPriority.getSsids().isEmpty()
197                 && !networkPriority.getSsids().contains(caps.getSsid())) {
198             return false;
199         }
200 
201         return true;
202     }
203 
isWifiRssiAcceptable( UnderlyingNetworkRecord networkRecord, UnderlyingNetworkRecord currentlySelected, PersistableBundleWrapper carrierConfig)204     private static boolean isWifiRssiAcceptable(
205             UnderlyingNetworkRecord networkRecord,
206             UnderlyingNetworkRecord currentlySelected,
207             PersistableBundleWrapper carrierConfig) {
208         final NetworkCapabilities caps = networkRecord.networkCapabilities;
209         final boolean isSelectedNetwork =
210                 currentlySelected != null
211                         && networkRecord.network.equals(currentlySelected.network);
212 
213         if (isSelectedNetwork
214                 && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
215             return true;
216         }
217 
218         if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
219             return true;
220         }
221 
222         return false;
223     }
224 
225     @VisibleForTesting(visibility = Visibility.PRIVATE)
checkMatchesCellPriorityRule( VcnContext vcnContext, VcnCellUnderlyingNetworkTemplate networkPriority, UnderlyingNetworkRecord networkRecord, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot)226     public static boolean checkMatchesCellPriorityRule(
227             VcnContext vcnContext,
228             VcnCellUnderlyingNetworkTemplate networkPriority,
229             UnderlyingNetworkRecord networkRecord,
230             ParcelUuid subscriptionGroup,
231             TelephonySubscriptionSnapshot snapshot) {
232         final NetworkCapabilities caps = networkRecord.networkCapabilities;
233 
234         if (!caps.hasTransport(TRANSPORT_CELLULAR)) {
235             return false;
236         }
237 
238         final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
239                 ((TelephonyNetworkSpecifier) caps.getNetworkSpecifier());
240         if (telephonyNetworkSpecifier == null) {
241             logWtf("Got null NetworkSpecifier");
242             return false;
243         }
244 
245         final int subId = telephonyNetworkSpecifier.getSubscriptionId();
246         final TelephonyManager subIdSpecificTelephonyMgr =
247                 vcnContext
248                         .getContext()
249                         .getSystemService(TelephonyManager.class)
250                         .createForSubscriptionId(subId);
251 
252         if (!networkPriority.getOperatorPlmnIds().isEmpty()) {
253             final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
254             if (!networkPriority.getOperatorPlmnIds().contains(plmnId)) {
255                 return false;
256             }
257         }
258 
259         if (!networkPriority.getSimSpecificCarrierIds().isEmpty()) {
260             final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId();
261             if (!networkPriority.getSimSpecificCarrierIds().contains(carrierId)) {
262                 return false;
263             }
264         }
265 
266         final int roamingMatch = networkPriority.getRoaming();
267         final boolean isRoaming = !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
268         if (roamingMatch == MATCH_REQUIRED && !isRoaming
269                 || roamingMatch == MATCH_FORBIDDEN && isRoaming) {
270             return false;
271         }
272 
273         final int opportunisticMatch = networkPriority.getOpportunistic();
274         final boolean isOpportunistic = isOpportunistic(snapshot, caps.getSubscriptionIds());
275         if (opportunisticMatch == MATCH_REQUIRED) {
276             if (!isOpportunistic) {
277                 return false;
278             }
279 
280             // If this carrier is the active data provider, ensure that opportunistic is only
281             // ever prioritized if it is also the active data subscription. This ensures that
282             // if an opportunistic subscription is still in the process of being switched to,
283             // or switched away from, the VCN does not attempt to continue using it against the
284             // decision made at the telephony layer. Failure to do so may result in the modem
285             // switching back and forth.
286             //
287             // Allow the following two cases:
288             // 1. Active subId is NOT in the group that this VCN is supporting
289             // 2. This opportunistic subscription is for the active subId
290             if (snapshot.getAllSubIdsInGroup(subscriptionGroup)
291                             .contains(SubscriptionManager.getActiveDataSubscriptionId())
292                     && !caps.getSubscriptionIds()
293                             .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
294                 return false;
295             }
296         } else if (opportunisticMatch == MATCH_FORBIDDEN && !isOpportunistic) {
297             return false;
298         }
299 
300         return true;
301     }
302 
isOpportunistic( @onNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds)303     static boolean isOpportunistic(
304             @NonNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds) {
305         if (snapshot == null) {
306             logWtf("Got null snapshot");
307             return false;
308         }
309         for (int subId : subIds) {
310             if (snapshot.isOpportunistic(subId)) {
311                 return true;
312             }
313         }
314         return false;
315     }
316 
getWifiEntryRssiThreshold(@ullable PersistableBundleWrapper carrierConfig)317     static int getWifiEntryRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) {
318         if (carrierConfig != null) {
319             return carrierConfig.getInt(
320                     VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
321                     WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT);
322         }
323         return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
324     }
325 
getWifiExitRssiThreshold(@ullable PersistableBundleWrapper carrierConfig)326     static int getWifiExitRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) {
327         if (carrierConfig != null) {
328             return carrierConfig.getInt(
329                     VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
330                     WIFI_EXIT_RSSI_THRESHOLD_DEFAULT);
331         }
332         return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
333     }
334 
logWtf(String msg)335     private static void logWtf(String msg) {
336         Slog.wtf(TAG, msg);
337         LOCAL_LOG.log(TAG + " WTF: " + msg);
338     }
339 }
340