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