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