1 /* 2 * Copyright (C) 2019 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.internal.telephony.data; 18 19 import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; 20 import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG; 21 import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; 22 23 import android.annotation.NonNull; 24 import android.content.Context; 25 import android.net.ConnectivityManager; 26 import android.net.Network; 27 import android.net.NetworkCapabilities; 28 import android.net.NetworkRequest; 29 import android.net.TelephonyNetworkSpecifier; 30 import android.os.Handler; 31 import android.os.PersistableBundle; 32 import android.telephony.CarrierConfigManager; 33 import android.telephony.CellIdentity; 34 import android.telephony.CellIdentityLte; 35 import android.telephony.CellInfo; 36 import android.telephony.NetworkRegistrationInfo; 37 import android.telephony.SubscriptionManager; 38 import android.util.Log; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.telephony.Phone; 42 import com.android.internal.telephony.PhoneConfigurationManager; 43 import com.android.internal.telephony.PhoneFactory; 44 import com.android.internal.telephony.flags.FeatureFlags; 45 import com.android.internal.telephony.metrics.TelephonyMetrics; 46 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent; 47 import com.android.internal.telephony.subscription.SubscriptionInfoInternal; 48 import com.android.internal.telephony.subscription.SubscriptionManagerService; 49 50 import java.util.ArrayDeque; 51 import java.util.HashMap; 52 import java.util.Map; 53 import java.util.concurrent.TimeUnit; 54 55 /** 56 * This class will validate whether cellular network verified by Connectivity's 57 * validation process. It listens request on a specific subId, sends a network request 58 * to Connectivity and listens to its callback or timeout. 59 */ 60 public class CellularNetworkValidator { 61 private static final String LOG_TAG = "NetworkValidator"; 62 63 // States of validator. Only one validation can happen at once. 64 // IDLE: no validation going on. 65 private static final int STATE_IDLE = 0; 66 // VALIDATING: validation going on. 67 private static final int STATE_VALIDATING = 1; 68 // VALIDATED: validation is done and successful. 69 // Waiting for stopValidation() to release 70 // validation NetworkRequest. 71 private static final int STATE_VALIDATED = 2; 72 73 // Singleton instance. 74 private static CellularNetworkValidator sInstance; 75 @VisibleForTesting 76 public static final long MAX_VALIDATION_CACHE_TTL = TimeUnit.DAYS.toMillis(1); 77 78 private int mState = STATE_IDLE; 79 private int mSubId; 80 private boolean mRequireTestPass; 81 82 private ValidationCallback mValidationCallback; 83 private final Context mContext; 84 private final ConnectivityManager mConnectivityManager; 85 @VisibleForTesting 86 public Handler mHandler = new Handler(); 87 @VisibleForTesting 88 public ConnectivityNetworkCallback mNetworkCallback; 89 private final ValidatedNetworkCache mValidatedNetworkCache = new ValidatedNetworkCache(); 90 91 @NonNull 92 private final FeatureFlags mFlags; 93 private class ValidatedNetworkCache { 94 // A cache with fixed size. It remembers 10 most recently successfully validated networks. 95 private static final int VALIDATED_NETWORK_CACHE_SIZE = 10; 96 97 private final ArrayDeque<ValidatedNetwork> mValidatedNetworkAQ = new ArrayDeque<>(); 98 private final Map<String, ValidatedNetwork> mValidatedNetworkMap = new HashMap<>(); 99 100 private static final class ValidatedNetwork { ValidatedNetwork(String identity, long timeStamp)101 ValidatedNetwork(String identity, long timeStamp) { 102 mValidationIdentity = identity; 103 mValidationTimeStamp = timeStamp; 104 } update(long timeStamp)105 void update(long timeStamp) { 106 mValidationTimeStamp = timeStamp; 107 } 108 final String mValidationIdentity; 109 long mValidationTimeStamp; 110 } 111 isRecentlyValidated(int subId)112 synchronized boolean isRecentlyValidated(int subId) { 113 long cacheTtl = getValidationCacheTtl(subId); 114 String networkIdentity = getValidationNetworkIdentity(subId); 115 if (networkIdentity == null || !mValidatedNetworkMap.containsKey(networkIdentity)) { 116 return false; 117 } 118 long validatedTime = mValidatedNetworkMap.get(networkIdentity).mValidationTimeStamp; 119 boolean recentlyValidated = System.currentTimeMillis() - validatedTime < cacheTtl; 120 logd("isRecentlyValidated on subId " + subId + " ? " + recentlyValidated); 121 return recentlyValidated; 122 } 123 124 synchronized void storeLastValidationResult(int subId, boolean validated) { 125 String networkIdentity = getValidationNetworkIdentity(subId); 126 logd("storeLastValidationResult for subId " + subId 127 + (validated ? " validated." : " not validated.")); 128 if (networkIdentity == null) return; 129 130 if (!validated) { 131 // If validation failed, clear it from the cache. 132 mValidatedNetworkAQ.remove(mValidatedNetworkMap.get(networkIdentity)); 133 mValidatedNetworkMap.remove(networkIdentity); 134 return; 135 } 136 long time = System.currentTimeMillis(); 137 ValidatedNetwork network = mValidatedNetworkMap.get(networkIdentity); 138 if (network != null) { 139 // Already existed in cache, update. 140 network.update(time); 141 // Re-add to re-sort. 142 mValidatedNetworkAQ.remove(network); 143 mValidatedNetworkAQ.add(network); 144 } else { 145 network = new ValidatedNetwork(networkIdentity, time); 146 mValidatedNetworkMap.put(networkIdentity, network); 147 mValidatedNetworkAQ.add(network); 148 } 149 // If exceeded max size, remove the one with smallest validation timestamp. 150 if (mValidatedNetworkAQ.size() > VALIDATED_NETWORK_CACHE_SIZE) { 151 ValidatedNetwork networkToRemove = mValidatedNetworkAQ.poll(); 152 mValidatedNetworkMap.remove(networkToRemove.mValidationIdentity); 153 } 154 } 155 156 private String getValidationNetworkIdentity(int subId) { 157 if (!SubscriptionManager.isUsableSubscriptionId(subId)) return null; 158 Phone phone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance() 159 .getPhoneId(subId)); 160 if (phone == null || phone.getServiceState() == null) return null; 161 162 NetworkRegistrationInfo regInfo = phone.getServiceState().getNetworkRegistrationInfo( 163 DOMAIN_PS, TRANSPORT_TYPE_WWAN); 164 if (regInfo == null || regInfo.getCellIdentity() == null) return null; 165 166 CellIdentity cellIdentity = regInfo.getCellIdentity(); 167 // TODO: add support for other technologies. 168 if (cellIdentity.getType() != CellInfo.TYPE_LTE 169 || cellIdentity.getMccString() == null || cellIdentity.getMncString() == null 170 || ((CellIdentityLte) cellIdentity).getTac() == CellInfo.UNAVAILABLE) { 171 return null; 172 } 173 174 return cellIdentity.getMccString() + cellIdentity.getMncString() + "_" 175 + ((CellIdentityLte) cellIdentity).getTac() + "_" + subId; 176 } 177 178 private long getValidationCacheTtl(int subId) { 179 long ttl = 0; 180 CarrierConfigManager configManager = (CarrierConfigManager) 181 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); 182 if (configManager != null) { 183 PersistableBundle b = configManager.getConfigForSubId(subId); 184 if (b != null) { 185 ttl = b.getLong(KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG); 186 } 187 } 188 // Ttl can't be bigger than one day for now. 189 return Math.min(ttl, MAX_VALIDATION_CACHE_TTL); 190 } 191 } 192 193 /** 194 * Callback to pass in when starting validation. 195 */ 196 public interface ValidationCallback { 197 /** 198 * Validation failed, passed or timed out. 199 */ 200 void onValidationDone(boolean validated, int subId); 201 /** 202 * Called when a corresponding network becomes available. 203 */ 204 void onNetworkAvailable(Network network, int subId); 205 } 206 207 /** 208 * Create instance. 209 */ 210 public static CellularNetworkValidator make(Context context, 211 @NonNull FeatureFlags featureFlags) { 212 if (sInstance != null) { 213 logd("createCellularNetworkValidator failed. Instance already exists."); 214 } else { 215 sInstance = new CellularNetworkValidator(context, featureFlags); 216 } 217 218 return sInstance; 219 } 220 221 /** 222 * Get instance. 223 */ 224 public static CellularNetworkValidator getInstance() { 225 return sInstance; 226 } 227 228 /** 229 * Check whether this feature is supported or not. 230 */ 231 public boolean isValidationFeatureSupported() { 232 return PhoneConfigurationManager.getInstance().getCurrentPhoneCapability() 233 .isNetworkValidationBeforeSwitchSupported(); 234 } 235 236 @VisibleForTesting 237 public CellularNetworkValidator(Context context, @NonNull FeatureFlags featureFlags) { 238 mContext = context; 239 mFlags = featureFlags; 240 mConnectivityManager = (ConnectivityManager) 241 mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 242 } 243 244 /** 245 * API to start a validation 246 */ 247 public synchronized void validate(int subId, long timeoutInMs, 248 boolean requireTestPass, ValidationCallback callback) { 249 // If it's already validating the same subscription, do nothing. 250 if (subId == mSubId) return; 251 252 SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() 253 .getSubscriptionInfoInternal(subId); 254 if (subInfo == null || !subInfo.isActive()) { 255 logd("Failed to start validation. Inactive subId " + subId); 256 callback.onValidationDone(false, subId); 257 return; 258 } 259 260 if (isValidating()) { 261 stopValidation(); 262 } 263 264 mState = STATE_VALIDATING; 265 mSubId = subId; 266 mValidationCallback = callback; 267 mRequireTestPass = requireTestPass; 268 269 logd("Start validating subId " + mSubId + " timeoutInMs " + timeoutInMs 270 + " mRequireTestPass " + mRequireTestPass); 271 272 mNetworkCallback = new ConnectivityNetworkCallback(subId); 273 274 mConnectivityManager.requestNetwork( 275 createNetworkRequest(subId), mNetworkCallback, mHandler); 276 mHandler.postDelayed(() -> onValidationTimeout(subId), timeoutInMs); 277 } 278 279 private synchronized void onValidationTimeout(int subId) { 280 logd("timeout on subId " + subId + " validation."); 281 // Remember latest validated network. 282 mValidatedNetworkCache.storeLastValidationResult(subId, false); 283 reportValidationResult(false, subId); 284 } 285 286 /** 287 * API to stop the current validation. 288 */ 289 public synchronized void stopValidation() { 290 if (!isValidating()) { 291 logd("No need to stop validation."); 292 return; 293 } 294 if (mNetworkCallback != null) { 295 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 296 } 297 mState = STATE_IDLE; 298 mHandler.removeCallbacksAndMessages(null); 299 mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 300 } 301 302 /** 303 * Return which subscription is under validating. 304 */ 305 public synchronized int getSubIdInValidation() { 306 return mSubId; 307 } 308 309 /** 310 * Return whether there's an ongoing validation. 311 */ 312 public synchronized boolean isValidating() { 313 return mState != STATE_IDLE; 314 } 315 316 private NetworkRequest createNetworkRequest(int subId) { 317 NetworkRequest.Builder req = new NetworkRequest.Builder() 318 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 319 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) 320 .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() 321 .setSubscriptionId(subId).build()); 322 323 // Satellite is considered valid as long as it can serve restricted requests. 324 Phone target = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId)); 325 boolean isSatellite = target != null 326 && target.getServiceState().isUsingNonTerrestrialNetwork(); 327 if (isSatellite) { 328 req.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE) 329 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); 330 } 331 return req.build(); 332 } 333 334 private synchronized void reportValidationResult(boolean passed, int subId) { 335 // If the validation result is not for current subId, do nothing. 336 if (mSubId != subId) return; 337 338 mHandler.removeCallbacksAndMessages(null); 339 340 // Deal with the result only when state is still VALIDATING. This is to avoid 341 // receiving multiple callbacks in queue. 342 if (mState == STATE_VALIDATING) { 343 mValidationCallback.onValidationDone(passed, mSubId); 344 mState = STATE_VALIDATED; 345 boolean keepRequest = passed || !mRequireTestPass; 346 // If validation passed and per request to NOT release after validation, delay cleanup. 347 if (keepRequest) { 348 mHandler.postDelayed(this::stopValidation, 5000); 349 } else { 350 stopValidation(); 351 } 352 353 TelephonyMetrics.getInstance().writeNetworkValidate(passed 354 ? TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_PASSED 355 : TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_FAILED); 356 } 357 } 358 359 private synchronized void reportNetworkAvailable(Network network, int subId) { 360 // If the validation result is not for current subId, do nothing. 361 if (mSubId != subId) return; 362 mValidationCallback.onNetworkAvailable(network, subId); 363 } 364 365 @VisibleForTesting 366 public class ConnectivityNetworkCallback extends ConnectivityManager.NetworkCallback { 367 private final int mSubId; 368 369 ConnectivityNetworkCallback(int subId) { 370 mSubId = subId; 371 } 372 /** 373 * ConnectivityManager.NetworkCallback implementation 374 */ 375 @Override 376 public void onAvailable(@NonNull Network network) { 377 logd("network onAvailable " + network); 378 TelephonyMetrics.getInstance().writeNetworkValidate( 379 TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_AVAILABLE); 380 // If it hits validation cache, we report as validation passed; otherwise we report 381 // network is available. 382 if (mValidatedNetworkCache.isRecentlyValidated(mSubId)) { 383 reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId); 384 } else { 385 reportNetworkAvailable(network, ConnectivityNetworkCallback.this.mSubId); 386 } 387 } 388 389 @Override 390 public void onLosing(@NonNull Network network, int maxMsToLive) { 391 logd("network onLosing " + network + " maxMsToLive " + maxMsToLive); 392 mValidatedNetworkCache.storeLastValidationResult( 393 ConnectivityNetworkCallback.this.mSubId, false); 394 reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId); 395 } 396 397 @Override 398 public void onLost(@NonNull Network network) { 399 logd("network onLost " + network); 400 mValidatedNetworkCache.storeLastValidationResult( 401 ConnectivityNetworkCallback.this.mSubId, false); 402 reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId); 403 } 404 405 @Override 406 public void onUnavailable() { 407 logd("onUnavailable"); 408 mValidatedNetworkCache.storeLastValidationResult( 409 ConnectivityNetworkCallback.this.mSubId, false); 410 reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId); 411 } 412 413 @Override 414 public void onCapabilitiesChanged(@NonNull Network network, 415 NetworkCapabilities networkCapabilities) { 416 if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) { 417 logd("onValidated"); 418 mValidatedNetworkCache.storeLastValidationResult( 419 ConnectivityNetworkCallback.this.mSubId, true); 420 reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId); 421 } 422 } 423 } 424 425 private static void logd(String log) { 426 Log.d(LOG_TAG, log); 427 } 428 } 429