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