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