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