1 /* 2 * Copyright (C) 2023 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.server.wifi; 18 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 21 22 import static com.android.server.wifi.entitlement.CarrierSpecificServiceEntitlement.FAILURE_REASON_NAME; 23 import static com.android.server.wifi.entitlement.CarrierSpecificServiceEntitlement.REASON_HTTPS_CONNECTION_FAILURE; 24 import static com.android.server.wifi.entitlement.CarrierSpecificServiceEntitlement.REASON_TRANSIENT_FAILURE; 25 26 import android.annotation.NonNull; 27 import android.net.ConnectivityManager; 28 import android.net.Network; 29 import android.net.NetworkCapabilities; 30 import android.net.wifi.WifiConfiguration; 31 import android.net.wifi.WifiContext; 32 import android.net.wifi.WifiStringResourceWrapper; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.os.Message; 36 import android.os.Process; 37 import android.telephony.SubscriptionManager; 38 import android.text.TextUtils; 39 import android.util.ArraySet; 40 import android.util.Log; 41 import android.util.SparseArray; 42 import android.util.SparseIntArray; 43 import android.util.SparseLongArray; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.server.wifi.entitlement.CarrierSpecificServiceEntitlement; 47 import com.android.server.wifi.entitlement.PseudonymInfo; 48 49 import java.net.MalformedURLException; 50 import java.time.Duration; 51 import java.util.Optional; 52 import java.util.Random; 53 import java.util.Set; 54 55 /** 56 * Manages the OOB and in-band pseudonyms 57 */ 58 public final class WifiPseudonymManager { 59 private static final String TAG = "WifiPseudonymManager"; 60 public static final String CONFIG_SERVER_URL = 61 "config_wifiOobPseudonymEntitlementServerUrl"; 62 @VisibleForTesting 63 static final long TEN_SECONDS_IN_MILLIS = Duration.ofSeconds(10).toMillis(); 64 @VisibleForTesting 65 private static final long SEVEN_DAYS_IN_MILLIS = Duration.ofDays(7).toMillis(); 66 @VisibleForTesting 67 static final long[] RETRY_INTERVALS_FOR_SERVER_ERROR = { 68 Duration.ofMinutes(5).toMillis(), 69 Duration.ofMinutes(15).toMillis(), 70 Duration.ofMinutes(30).toMillis(), 71 Duration.ofMinutes(60).toMillis(), 72 Duration.ofMinutes(120).toMillis()}; 73 @VisibleForTesting 74 static final long[] RETRY_INTERVALS_FOR_CONNECTION_ERROR = { 75 Duration.ofSeconds(30).toMillis(), 76 Duration.ofMinutes(1).toMillis(), 77 Duration.ofHours(1).toMillis(), 78 Duration.ofHours(3).toMillis(), 79 Duration.ofHours(9).toMillis()}; 80 private final WifiContext mWifiContext; 81 private final WifiInjector mWifiInjector; 82 private final Clock mClock; 83 private final Handler mWifiHandler; 84 85 private boolean mVerboseLogEnabled = false; 86 87 /** 88 * Cached Map of <carrier ID, PseudonymInfo>. 89 */ 90 private final SparseArray<PseudonymInfo> mPseudonymInfoArray = new SparseArray<>(); 91 92 /* 93 * Two cached map of <carrier ID, retry times>. 94 */ 95 private final SparseIntArray mRetryTimesArrayForServerError = new SparseIntArray(); 96 private final SparseIntArray mRetryTimesArrayForConnectionError = new SparseIntArray(); 97 98 /* 99 * Cached map of <carrier ID, last failure time stamp>. 100 */ 101 @VisibleForTesting 102 final SparseLongArray mLastFailureTimestampArray = new SparseLongArray(); 103 104 /* 105 * This set contains all the carrier IDs which we should retrieve OOB pseudonym for when the 106 * data network becomes available. 107 */ 108 private final Set<Integer> mPendingToRetrieveSet = new ArraySet<>(); 109 110 private final ConnectivityManager.NetworkCallback mNetworkCallback = 111 new ConnectivityManager.NetworkCallback() { 112 @Override 113 public void onCapabilitiesChanged(@NonNull Network network, 114 @NonNull NetworkCapabilities networkCapabilities) { 115 if (networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET) 116 && networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { 117 retrieveAllNeededOobPseudonym(); 118 ConnectivityManager cm = mWifiContext.getSystemService( 119 ConnectivityManager.class); 120 if (cm != null) { 121 cm.unregisterNetworkCallback(mNetworkCallback); 122 } 123 } 124 } 125 }; 126 127 @VisibleForTesting 128 final CarrierSpecificServiceEntitlement.Callback mRetrieveCallback = 129 new RetrieveCallback(); 130 private final Set<PseudonymUpdatingListener> mPseudonymUpdatingListeners = 131 new ArraySet<>(); 132 WifiPseudonymManager(@onNull WifiContext wifiContext, @NonNull WifiInjector wifiInjector, @NonNull Clock clock, @NonNull Looper wifiLooper)133 WifiPseudonymManager(@NonNull WifiContext wifiContext, @NonNull WifiInjector wifiInjector, 134 @NonNull Clock clock, @NonNull Looper wifiLooper) { 135 mWifiContext = wifiContext; 136 mWifiInjector = wifiInjector; 137 mClock = clock; 138 // Create a new handler to have a dedicated message queue. 139 mWifiHandler = new Handler(wifiLooper); 140 } 141 142 /** 143 * Gets the valid PseudonymInfo for given carrier ID 144 * 145 * @param carrierId carrier id for target carrier. 146 * @return Optional of the matched PseudonymInfo. 147 */ getValidPseudonymInfo(int carrierId)148 public Optional<PseudonymInfo> getValidPseudonymInfo(int carrierId) { 149 Optional<PseudonymInfo> optionalPseudonymInfo = getPseudonymInfo(carrierId); 150 if (optionalPseudonymInfo.isEmpty()) { 151 return Optional.empty(); 152 } 153 154 PseudonymInfo pseudonymInfo = optionalPseudonymInfo.get(); 155 if (pseudonymInfo.hasExpired()) { 156 return Optional.empty(); 157 } 158 159 WifiCarrierInfoManager wifiCarrierInfoManager = mWifiInjector.getWifiCarrierInfoManager(); 160 WifiCarrierInfoManager.SimInfo simInfo = 161 wifiCarrierInfoManager.getSimInfo( 162 wifiCarrierInfoManager.getMatchingSubId(carrierId)); 163 String imsi = simInfo == null ? null : simInfo.imsi; 164 if (imsi == null) { 165 Log.e(TAG, "Matched IMSI is null for carrierId " + carrierId); 166 return Optional.empty(); 167 } 168 if (!imsi.equalsIgnoreCase(pseudonymInfo.getImsi())) { 169 Log.e(TAG, "IMSI doesn't match for carrierId " + carrierId); 170 return Optional.empty(); 171 } 172 return optionalPseudonymInfo; 173 } 174 getPseudonymInfo(int carrierId)175 private Optional<PseudonymInfo> getPseudonymInfo(int carrierId) { 176 PseudonymInfo pseudonymInfo; 177 pseudonymInfo = mPseudonymInfoArray.get(carrierId); 178 vlogd("getPseudonymInfo(" + carrierId + ") = " + pseudonymInfo); 179 return Optional.ofNullable(pseudonymInfo); 180 } 181 182 /** 183 * Retrieves the OOB pseudonym as a safe check if there isn't any valid pseudonym available, 184 * and it has passed 7 days since the last retrieval failure. 185 * 186 * If there was some problem in the service entitlement server, all the retries to retrieve the 187 * pseudonym had failed. Then the carrier fixes the service entitlement server's issue. But 188 * the device will never connect to this carrier's WiFi until the user reboot the device or swap 189 * the sim. With this safe check, our device will retry to retrieve the OOB pseudonym every 7 190 * days if the last retrieval has failed and the device is in this carrier's WiFi coverage. 191 */ retrievePseudonymOnFailureTimeoutExpired( @onNull WifiConfiguration wifiConfiguration)192 public void retrievePseudonymOnFailureTimeoutExpired( 193 @NonNull WifiConfiguration wifiConfiguration) { 194 if (wifiConfiguration.enterpriseConfig == null 195 || !wifiConfiguration.enterpriseConfig.isAuthenticationSimBased()) { 196 return; 197 } 198 retrievePseudonymOnFailureTimeoutExpired(wifiConfiguration.carrierId); 199 } 200 201 /** 202 * Retrieves the OOP pseudonym as a safe check if there isn't any valid pseudonym available, 203 * and it has passed 7 days since the last retrieval failure. 204 * @param carrierId The caller must be a SIM based wifi configuration or passpoint. 205 */ retrievePseudonymOnFailureTimeoutExpired(int carrierId)206 public void retrievePseudonymOnFailureTimeoutExpired(int carrierId) { 207 if (!mWifiInjector.getWifiCarrierInfoManager().isOobPseudonymFeatureEnabled(carrierId)) { 208 return; 209 } 210 Optional<PseudonymInfo> optionalPseudonymInfo = getValidPseudonymInfo(carrierId); 211 if (optionalPseudonymInfo.isPresent()) { 212 return; 213 } 214 long timeStamp = mLastFailureTimestampArray.get(carrierId); 215 if ((timeStamp > 0) 216 && (mClock.getWallClockMillis() - timeStamp >= SEVEN_DAYS_IN_MILLIS)) { 217 scheduleToRetrieveDelayed(carrierId, 0); 218 } 219 } 220 221 /** 222 * Registers a {@link PseudonymUpdatingListener}. 223 */ registerPseudonymUpdatingListener(PseudonymUpdatingListener listener)224 public void registerPseudonymUpdatingListener(PseudonymUpdatingListener listener) { 225 mPseudonymUpdatingListeners.add(listener); 226 } 227 228 /** 229 * Unregisters the {@link PseudonymUpdatingListener}. 230 */ unregisterPseudonymUpdatingListener(PseudonymUpdatingListener listener)231 public void unregisterPseudonymUpdatingListener(PseudonymUpdatingListener listener) { 232 mPseudonymUpdatingListeners.remove(listener); 233 } 234 235 /** 236 * Update the input WifiConfiguration's anonymous identity. 237 * 238 * @param wifiConfiguration WifiConfiguration which will be updated. 239 * @return true if there is a valid pseudonym to update the WifiConfiguration, otherwise false. 240 */ updateWifiConfiguration(@onNull WifiConfiguration wifiConfiguration)241 public void updateWifiConfiguration(@NonNull WifiConfiguration wifiConfiguration) { 242 if (wifiConfiguration.enterpriseConfig == null 243 || !wifiConfiguration.enterpriseConfig.isAuthenticationSimBased()) { 244 return; 245 } 246 if (!mWifiInjector.getWifiCarrierInfoManager() 247 .isOobPseudonymFeatureEnabled(wifiConfiguration.carrierId)) { 248 return; 249 } 250 WifiCarrierInfoManager wifiCarrierInfoManager = mWifiInjector.getWifiCarrierInfoManager(); 251 Optional<PseudonymInfo> optionalPseudonymInfo = 252 getValidPseudonymInfo(wifiConfiguration.carrierId); 253 if (optionalPseudonymInfo.isEmpty()) { 254 Log.w(TAG, "pseudonym is not available, the wifi configuration: " 255 + wifiConfiguration.getKey() + " can not be updated."); 256 return; 257 } 258 259 String pseudonym = optionalPseudonymInfo.get().getPseudonym(); 260 String expectedIdentity = 261 wifiCarrierInfoManager.decoratePseudonymWith3GppRealm(wifiConfiguration, 262 pseudonym); 263 String existingIdentity = wifiConfiguration.enterpriseConfig.getAnonymousIdentity(); 264 if (TextUtils.equals(expectedIdentity, existingIdentity)) { 265 return; 266 } 267 268 wifiConfiguration.enterpriseConfig.setAnonymousIdentity(expectedIdentity); 269 vlogd("update pseudonym: " + maskPseudonym(pseudonym) 270 + " for wifi config: " + wifiConfiguration.getKey()); 271 mWifiInjector.getWifiConfigManager() 272 .addOrUpdateNetwork(wifiConfiguration, Process.WIFI_UID); 273 if (wifiConfiguration.isPasspoint()) { 274 mWifiInjector.getPasspointManager().setAnonymousIdentity(wifiConfiguration); 275 } else if (wifiConfiguration.fromWifiNetworkSuggestion) { 276 mWifiInjector.getWifiNetworkSuggestionsManager() 277 .setAnonymousIdentity(wifiConfiguration); 278 } 279 } 280 281 /** 282 * If the OOB Pseudonym feature supports the WifiConfiguration, enable the 283 * strict conservative peer mode. 284 */ enableStrictConservativePeerModeIfSupported( @onNull WifiConfiguration wifiConfiguration)285 public void enableStrictConservativePeerModeIfSupported( 286 @NonNull WifiConfiguration wifiConfiguration) { 287 if (wifiConfiguration.enterpriseConfig == null) { 288 return; 289 } 290 if (wifiConfiguration.enterpriseConfig.isAuthenticationSimBased() 291 && mWifiInjector.getWifiCarrierInfoManager() 292 .isOobPseudonymFeatureEnabled(wifiConfiguration.carrierId)) { 293 wifiConfiguration.enterpriseConfig.setStrictConservativePeerMode(true); 294 } 295 } 296 297 /** 298 * Set in-band pseudonym with the existing PseudonymInfo's TTL. When an in-band pseudonym is 299 * received, there should already be an existing pseudonym(in-band or OOB). 300 * 301 * @param carrierId carrier id for target carrier. 302 * @param pseudonym Pseudonym to set for the target carrier. 303 */ setInBandPseudonym(int carrierId, @NonNull String pseudonym)304 public void setInBandPseudonym(int carrierId, @NonNull String pseudonym) { 305 vlogd("setInBandPseudonym(" + carrierId + ", " + maskPseudonym(pseudonym) + ")"); 306 Optional<PseudonymInfo> current = getPseudonymInfo(carrierId); 307 if (current.isPresent()) { 308 setPseudonymAndScheduleRefresh(carrierId, 309 new PseudonymInfo(pseudonym, current.get().getImsi(), 310 current.get().getTtlInMillis())); 311 } else { 312 Log.wtf(TAG, "setInBandPseudonym() is called without an existing pseudonym!"); 313 } 314 } 315 316 /* 317 * Sets pseudonym(OOB or in-band) into mPseudonymInfoArray and schedule to refresh it after it 318 * expires. 319 */ 320 @VisibleForTesting setPseudonymAndScheduleRefresh(int carrierId, @NonNull PseudonymInfo pseudonymInfo)321 void setPseudonymAndScheduleRefresh(int carrierId, @NonNull PseudonymInfo pseudonymInfo) { 322 mPseudonymInfoArray.put(carrierId, pseudonymInfo); 323 // Cancel all the queued messages and queue another message to refresh the pseudonym 324 mWifiHandler.removeMessages(carrierId); 325 scheduleToRetrieveDelayed(carrierId, pseudonymInfo.getAtrInMillis()); 326 } 327 328 /** 329 * Retrieves the OOB pseudonym if there is no pseudonym or the existing pseudonym has expired. 330 * This method is called when the CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED is 331 * received or the TTL has elapsed to refresh the OOB pseudonym. 332 * 333 * @param carrierId carrier id for target carrier 334 */ retrieveOobPseudonymIfNeeded(int carrierId)335 public void retrieveOobPseudonymIfNeeded(int carrierId) { 336 vlogd("retrieveOobPseudonymIfNeeded(" + carrierId + ")"); 337 Optional<PseudonymInfo> optionalPseudonymInfo = getValidPseudonymInfo(carrierId); 338 if (optionalPseudonymInfo.isEmpty() || optionalPseudonymInfo.get().shouldBeRefreshed()) { 339 scheduleToRetrieveDelayed(carrierId, 0); 340 } else { 341 vlogd("Current pseudonym is still fresh. Exit."); 342 } 343 } 344 345 /** 346 * Retrieves the OOB pseudonym for all the existing carrierIds in mPseudonymInfoArray if needed. 347 * This method is called when the network becomes available. 348 */ retrieveAllNeededOobPseudonym()349 private void retrieveAllNeededOobPseudonym() { 350 vlogd("retrieveAllNeededOobPseudonym()"); 351 for (int carrierId : mPendingToRetrieveSet) { 352 retrieveOobPseudonymIfNeeded(carrierId); 353 } 354 mPendingToRetrieveSet.clear(); 355 } 356 357 /** 358 * Retrieves the OOB pseudonym with rate limit. 359 * This method is supposed to be called after the carrier's AAA server returns authentication 360 * error. It retrieves OOB pseudonym only if the existing pseudonym is old enough. 361 * 362 * Note: The authentication error only happens when there was already a valid pseudonym before. 363 * Otherwise, this Wi-Fi configuration won't be automatically connected and no authentication 364 * error will be received from AAA server. 365 */ retrieveOobPseudonymWithRateLimit(int carrierId)366 public void retrieveOobPseudonymWithRateLimit(int carrierId) { 367 vlogd("retrieveOobPseudonymWithRateLimit(" + carrierId + ")"); 368 Optional<PseudonymInfo> optionalPseudonymInfo = getPseudonymInfo(carrierId); 369 if (optionalPseudonymInfo.isEmpty()) { 370 Log.wtf(TAG, "The authentication error only happens when there was already a valid" 371 + " pseudonym before. But now there isn't any PseudonymInfo!"); 372 return; 373 } 374 if (optionalPseudonymInfo.get().isOldEnoughToRefresh()) { 375 // Schedule the work uniformly in [0..10) seconds to smooth out any potential surge. 376 scheduleToRetrieveDelayed(carrierId, 377 (new Random()).nextInt((int) TEN_SECONDS_IN_MILLIS)); 378 } 379 } 380 scheduleToRetrieveDelayed(int carrierId, long delayMillis)381 private void scheduleToRetrieveDelayed(int carrierId, long delayMillis) { 382 Message msg = Message.obtain(mWifiHandler, new RetrieveRunnable(carrierId)); 383 msg.what = carrierId; 384 mWifiHandler.sendMessageDelayed(msg, delayMillis); 385 /* 386 * Always suppose it fails before the retrieval really starts to prevent multiple messages 387 * been queued when there is no data network available to retrieve. After retrieving, this 388 * timestamp will be updated to 0(success) or failure timestamp. 389 */ 390 mLastFailureTimestampArray.put(carrierId, mClock.getWallClockMillis()); 391 } 392 getServerUrl(int subId, int carrierId)393 private String getServerUrl(int subId, int carrierId) { 394 WifiStringResourceWrapper wrapper = mWifiContext.getStringResourceWrapper(subId, carrierId); 395 return wrapper.getString(CONFIG_SERVER_URL, ""); 396 } 397 maskPseudonym(String pseudonym)398 private String maskPseudonym(String pseudonym) { 399 return (pseudonym.length() >= 7) ? (pseudonym.substring(0, 7) + "***") : pseudonym; 400 } 401 402 /** 403 * Enable/disable verbose logging. 404 */ enableVerboseLogging(boolean verboseEnabled)405 public void enableVerboseLogging(boolean verboseEnabled) { 406 mVerboseLogEnabled = verboseEnabled; 407 } 408 vlogd(String msg)409 private void vlogd(String msg) { 410 if (!mVerboseLogEnabled) { 411 return; 412 } 413 Log.d(TAG, msg); 414 } 415 416 @VisibleForTesting 417 class RetrieveRunnable implements Runnable { 418 @VisibleForTesting 419 int mCarrierId; 420 RetrieveRunnable(int carrierId)421 RetrieveRunnable(int carrierId) { 422 mCarrierId = carrierId; 423 } 424 425 @Override run()426 public void run() { 427 /* 428 * There may be multiple messages for this mCarrierId in the queue. There is no need to 429 * retrieve them multiple times. 430 * 431 * For example, carrierA's SIM is inserted into the device multiple times right before 432 * carrierA's pseudonym expires, there will be multiple messages in the queue. When the 433 * network becomes available, the pseudonym can't be retrieved because the carrierA's 434 * server reports an error. To prevent connecting with the server several times, we 435 * should cancel all the queued messages. 436 */ 437 mWifiHandler.removeMessages(mCarrierId); 438 439 if (!mWifiInjector.getWifiCarrierInfoManager() 440 .isOobPseudonymFeatureEnabled(mCarrierId)) { 441 vlogd("do nothing, OOB Pseudonym feature is not enabled for carrier: " 442 + mCarrierId); 443 return; 444 } 445 446 int subId = mWifiInjector.getWifiCarrierInfoManager().getMatchingSubId(mCarrierId); 447 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 448 Log.e(TAG, "RetrieveRunnable: " + mCarrierId + ": subId is invalid. Exit."); 449 return; 450 } 451 452 if (!isNetworkConnected()) { 453 if (mPendingToRetrieveSet.isEmpty()) { 454 ConnectivityManager cm = mWifiContext.getSystemService( 455 ConnectivityManager.class); 456 if (cm != null) { 457 cm.registerDefaultNetworkCallback(mNetworkCallback, mWifiHandler); 458 } 459 } 460 mPendingToRetrieveSet.add(mCarrierId); 461 return; 462 } 463 CarrierSpecificServiceEntitlement entitlement; 464 try { 465 entitlement = new CarrierSpecificServiceEntitlement(mWifiContext, subId, 466 getServerUrl(subId, mCarrierId)); 467 } catch (MalformedURLException e) { 468 Log.wtf(TAG, e.toString()); 469 return; 470 } 471 entitlement.getImsiPseudonym(mCarrierId, mWifiHandler, mRetrieveCallback); 472 } 473 isNetworkConnected()474 private boolean isNetworkConnected() { 475 ConnectivityManager cm = mWifiContext.getSystemService(ConnectivityManager.class); 476 Network activeNetwork = cm.getActiveNetwork(); 477 if (activeNetwork == null) { 478 return false; 479 } 480 481 NetworkCapabilities nc = cm.getNetworkCapabilities(activeNetwork); 482 if (nc == null) { 483 return false; 484 } 485 486 /* 487 * If we check only for "NET_CAPABILITY_INTERNET", we get "true" if we are connected 488 * to a Wi-Fi which has no access to the internet. "NET_CAPABILITY_VALIDATED" also 489 * verifies that we are online. 490 */ 491 return nc.hasCapability(NET_CAPABILITY_INTERNET) 492 && nc.hasCapability(NET_CAPABILITY_VALIDATED); 493 } 494 } 495 496 private class RetrieveCallback implements CarrierSpecificServiceEntitlement.Callback { 497 @Override onSuccess(int carrierId, PseudonymInfo pseudonymInfo)498 public void onSuccess(int carrierId, PseudonymInfo pseudonymInfo) { 499 vlogd("RetrieveCallback: OOB pseudonym is retrieved!!! for carrierId " + carrierId 500 + ": " + pseudonymInfo); 501 setPseudonymAndScheduleRefresh(carrierId, pseudonymInfo); 502 for (PseudonymUpdatingListener listener : mPseudonymUpdatingListeners) { 503 listener.onUpdated(carrierId, pseudonymInfo.getPseudonym()); 504 } 505 mLastFailureTimestampArray.put(carrierId, 0); 506 mRetryTimesArrayForConnectionError.put(carrierId, 0); 507 mRetryTimesArrayForServerError.put(carrierId, 0); 508 } 509 510 @Override onFailure(int carrierId, @CarrierSpecificServiceEntitlement.FailureReasonCode int reasonCode, String description)511 public void onFailure(int carrierId, 512 @CarrierSpecificServiceEntitlement.FailureReasonCode int reasonCode, 513 String description) { 514 Log.e(TAG, "RetrieveCallback.onFailure(" + carrierId + ", " 515 + FAILURE_REASON_NAME[reasonCode] + ", " + description); 516 mLastFailureTimestampArray.put(carrierId, mClock.getWallClockMillis()); 517 switch (reasonCode) { 518 case REASON_HTTPS_CONNECTION_FAILURE: 519 retryForConnectionError(carrierId); 520 break; 521 case REASON_TRANSIENT_FAILURE: 522 retryForServerError(carrierId); 523 break; 524 } 525 } 526 retryForConnectionError(int carrierId)527 private void retryForConnectionError(int carrierId) { 528 int retryTimes = mRetryTimesArrayForConnectionError.get(carrierId, 0); 529 if (retryTimes >= RETRY_INTERVALS_FOR_CONNECTION_ERROR.length) { 530 vlogd("It has reached the maximum retry count " 531 + RETRY_INTERVALS_FOR_CONNECTION_ERROR.length 532 + " for connection error. Exit."); 533 return; 534 } 535 long interval = RETRY_INTERVALS_FOR_CONNECTION_ERROR[retryTimes]; 536 retryTimes++; 537 mRetryTimesArrayForConnectionError.put(carrierId, retryTimes); 538 vlogd("retryForConnectionError: Schedule retry " + retryTimes + " in " 539 + interval + " milliseconds"); 540 scheduleToRetrieveDelayed(carrierId, interval); 541 } 542 retryForServerError(int carrierId)543 private void retryForServerError(int carrierId) { 544 int retryTimes = mRetryTimesArrayForServerError.get(carrierId, 0); 545 if (retryTimes >= RETRY_INTERVALS_FOR_SERVER_ERROR.length) { 546 vlogd("It has reached the maximum retry count " 547 + RETRY_INTERVALS_FOR_SERVER_ERROR.length + " for server error. Exit."); 548 return; 549 } 550 long interval = RETRY_INTERVALS_FOR_SERVER_ERROR[retryTimes]; 551 retryTimes++; 552 mRetryTimesArrayForServerError.put(carrierId, retryTimes); 553 vlogd("retryForServerError: Schedule retry " + retryTimes + " in " 554 + interval + " milliseconds"); 555 scheduleToRetrieveDelayed(carrierId, interval); 556 } 557 } 558 559 /** 560 * Listener to be notified the OOB pseudonym updating. 561 */ 562 public interface PseudonymUpdatingListener { 563 /** Notifies the pseudonym is updated. */ onUpdated(int carrierId, String pseudonym)564 void onUpdated(int carrierId, String pseudonym); 565 } 566 } 567