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