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