• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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