• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.Context;
23 import android.os.Build;
24 import android.os.PersistableBundle;
25 import android.os.SystemClock;
26 import android.os.SystemProperties;
27 import android.telephony.Annotation.ApnType;
28 import android.telephony.CarrierConfigManager;
29 import android.telephony.data.ApnSetting;
30 import android.telephony.data.DataCallResponse;
31 import android.text.TextUtils;
32 import android.util.Pair;
33 
34 import com.android.internal.telephony.dataconnection.DataThrottler;
35 import com.android.internal.telephony.util.TelephonyUtils;
36 import com.android.telephony.Rlog;
37 
38 import java.util.ArrayList;
39 import java.util.Random;
40 
41 /**
42  * Retry manager allows a simple way to declare a series of
43  * retry timeouts. After creating a RetryManager the configure
44  * method is used to define the sequence. A simple linear series
45  * may be initialized using configure with three integer parameters
46  * The other configure method allows a series to be declared using
47  * a string.
48  *<p>
49  * The format of the configuration string is the apn type followed by a series of parameters
50  * separated by a comma. There are two name value pair parameters plus a series
51  * of delay times. The units of of these delay times is unspecified.
52  * The name value pairs which may be specified are:
53  *<ul>
54  *<li>max_retries=<value>
55  *<li>default_randomizationTime=<value>
56  *</ul>
57  *<p>
58  * apn type specifies the APN type that the retry pattern will apply for. "others" is for all other
59  * APN types not specified in the config.
60  *
61  * max_retries is the number of times that incrementRetryCount
62  * maybe called before isRetryNeeded will return false. if value
63  * is infinite then isRetryNeeded will always return true.
64  *
65  * default_randomizationTime will be used as the randomizationTime
66  * for delay times which have no supplied randomizationTime. If
67  * default_randomizationTime is not defined it defaults to 0.
68  *<p>
69  * The other parameters define The series of delay times and each
70  * may have an optional randomization value separated from the
71  * delay time by a colon.
72  *<p>
73  * Examples:
74  * <ul>
75  * <li>3 retries for mms with no randomization value which means its 0:
76  * <ul><li><code>"mms:1000, 2000, 3000"</code></ul>
77  *
78  * <li>10 retries for default APN with a 500 default randomization value for each and
79  * the 4..10 retries all using 3000 as the delay:
80  * <ul><li><code>"default:max_retries=10, default_randomization=500, 1000, 2000, 3000"</code></ul>
81  *
82  * <li>4 retries for supl APN with a 100 as the default randomization value for the first 2 values
83  * and the other two having specified values of 500:
84  * <ul><li><code>"supl:default_randomization=100, 1000, 2000, 4000:500, 5000:500"</code></ul>
85  *
86  * <li>Infinite number of retries for all other APNs with the first one at 1000, the second at 2000
87  * all others will be at 3000.
88  * <ul><li><code>"others:max_retries=infinite,1000,2000,3000</code></ul>
89  * </ul>
90  *
91  * {@hide}
92  */
93 public class RetryManager {
94     public static final String LOG_TAG = "RetryManager";
95     public static final boolean DBG = true;
96     public static final boolean VDBG = false; // STOPSHIP if true
97 
98     /**
99      * The default retry configuration for APNs. See above for the syntax.
100      */
101     private static final String DEFAULT_DATA_RETRY_CONFIG = "max_retries=3, 5000, 5000, 5000";
102 
103     /**
104      * The APN type used for all other APNs retry configuration.
105      */
106     private static final String OTHERS_APN_TYPE = "others";
107 
108     /**
109      * The default value (in milliseconds) for delay between APN trying (mInterApnDelay)
110      * within the same round
111      */
112     private static final long DEFAULT_INTER_APN_DELAY = 20000;
113 
114     /**
115      * The default value (in milliseconds) for delay between APN trying (mFailFastInterApnDelay)
116      * within the same round when we are in fail fast mode
117      */
118     private static final long DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING = 3000;
119 
120     /**
121      * The default value (in milliseconds) for retrying APN after disconnect
122      */
123     private static final long DEFAULT_APN_RETRY_AFTER_DISCONNECT_DELAY = 10000;
124 
125     /**
126      * The value indicating retry should not occur.
127      */
128     public static final long NO_RETRY = Long.MAX_VALUE;
129 
130     /**
131      * The value indicating network did not suggest any retry delay
132      */
133     public static final long NO_SUGGESTED_RETRY_DELAY = DataCallResponse.RETRY_DURATION_UNDEFINED;
134 
135     /**
136      * If the network suggests a retry delay in the data call setup response, we will retry
137      * the current APN setting again. The maximum retry count is to prevent that network
138      * keeps asking device to retry data setup forever and causes power consumption issue.
139      */
140     private static final int DEFAULT_MAX_SAME_APN_RETRY = 3;
141 
142     /**
143      * The delay (in milliseconds) between APN trying within the same round
144      */
145     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
146     private long mInterApnDelay;
147 
148     /**
149      * The delay (in milliseconds) between APN trying within the same round when we are in
150      * fail fast mode
151      */
152     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
153     private long mFailFastInterApnDelay;
154 
155     /**
156      * The delay (in milliseconds) for APN retrying after disconnect (e.g. Modem suddenly reports
157      * data call lost)
158      */
159     private long mApnRetryAfterDisconnectDelay;
160 
161     /**
162      * The counter for same APN retrying. See {@link #DEFAULT_MAX_SAME_APN_RETRY} for the details.
163      */
164     private int mSameApnRetryCount = 0;
165 
166     /**
167      * The maximum times that frameworks retries data setup with the same APN. This value could be
168      * changed via carrier config. See {@link #DEFAULT_MAX_SAME_APN_RETRY} for the details.
169      */
170     private int mMaxSameApnRetry = DEFAULT_MAX_SAME_APN_RETRY;
171 
172     /**
173      * Retry record with times in milli-seconds
174      */
175     private static class RetryRec {
176         long mDelayTime;
177         long mRandomizationTime;
178 
RetryRec(long delayTime, long randomizationTime)179         RetryRec(long delayTime, long randomizationTime) {
180             mDelayTime = delayTime;
181             mRandomizationTime = randomizationTime;
182         }
183     }
184 
185     /**
186      * The array of retry records
187      */
188     private ArrayList<RetryRec> mRetryArray = new ArrayList<RetryRec>();
189 
190     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
191     private Phone mPhone;
192 
193     private final DataThrottler mDataThrottler;
194 
195     /**
196      * Flag indicating whether retrying forever regardless the maximum retry count mMaxRetryCount
197      */
198     private boolean mRetryForever = false;
199 
200     /**
201      * The maximum number of retries to attempt
202      */
203     private int mMaxRetryCount;
204 
205     /**
206      * The current number of retries
207      */
208     private int mRetryCount = 0;
209 
210     /**
211      * Random number generator. The random delay will be added into retry timer to avoid all devices
212      * around retrying the APN at the same time.
213      */
214     private Random mRng = new Random();
215 
216     /**
217      * Retry manager configuration string. See top of the detailed explanation.
218      */
219     private String mConfig;
220 
221     /**
222      * The list to store APN setting candidates for data call setup. Most of the carriers only have
223      * one APN, but few carriers have more than one.
224      */
225     private ArrayList<ApnSetting> mWaitingApns = new ArrayList<>();
226 
227     /**
228      * Index pointing to the current trying APN from mWaitingApns
229      */
230     private int mCurrentApnIndex = -1;
231 
232     /**
233      * Apn context type.
234      */
235     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
236     private String mApnType;
237 
238     private final @ApnType int apnType;
239 
240     /**
241      * Retry manager constructor
242      * @param phone Phone object
243      * @param dataThrottler Data throttler
244      * @param apnType APN type
245      */
RetryManager(@onNull Phone phone, @NonNull DataThrottler dataThrottler, @ApnType int apnType)246     public RetryManager(@NonNull Phone phone, @NonNull DataThrottler dataThrottler,
247             @ApnType int apnType) {
248         mPhone = phone;
249         mDataThrottler = dataThrottler;
250         this.apnType = apnType;
251     }
252 
253     /**
254      * Configure for using string which allow arbitrary
255      * sequences of times. See class comments for the
256      * string format.
257      *
258      * @return true if successful
259      */
260     @UnsupportedAppUsage
configure(String configStr)261     private boolean configure(String configStr) {
262         // Strip quotes if present.
263         if ((configStr.startsWith("\"") && configStr.endsWith("\""))) {
264             configStr = configStr.substring(1, configStr.length() - 1);
265         }
266 
267         // Reset the retry manager since delay, max retry count, etc...will be reset.
268         reset();
269 
270         if (DBG) log("configure: '" + configStr + "'");
271         mConfig = configStr;
272 
273         if (!TextUtils.isEmpty(configStr)) {
274             long defaultRandomization = 0;
275 
276             if (VDBG) log("configure: not empty");
277 
278             String strArray[] = configStr.split(",");
279             for (int i = 0; i < strArray.length; i++) {
280                 if (VDBG) log("configure: strArray[" + i + "]='" + strArray[i] + "'");
281                 Pair<Boolean, Integer> value;
282                 String splitStr[] = strArray[i].split("=", 2);
283                 splitStr[0] = splitStr[0].trim();
284                 if (VDBG) log("configure: splitStr[0]='" + splitStr[0] + "'");
285                 if (splitStr.length > 1) {
286                     splitStr[1] = splitStr[1].trim();
287                     if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
288                     if (TextUtils.equals(splitStr[0], "default_randomization")) {
289                         value = parseNonNegativeInt(splitStr[0], splitStr[1]);
290                         if (!value.first) return false;
291                         defaultRandomization = value.second;
292                     } else if (TextUtils.equals(splitStr[0], "max_retries")) {
293                         if (TextUtils.equals("infinite", splitStr[1])) {
294                             mRetryForever = true;
295                         } else {
296                             value = parseNonNegativeInt(splitStr[0], splitStr[1]);
297                             if (!value.first) return false;
298                             mMaxRetryCount = value.second;
299                         }
300                     } else {
301                         Rlog.e(LOG_TAG, "Unrecognized configuration name value pair: "
302                                         + strArray[i]);
303                         return false;
304                     }
305                 } else {
306                     /**
307                      * Assume a retry time with an optional randomization value
308                      * following a ":"
309                      */
310                     splitStr = strArray[i].split(":", 2);
311                     splitStr[0] = splitStr[0].trim();
312                     RetryRec rr = new RetryRec(0, 0);
313                     value = parseNonNegativeInt("delayTime", splitStr[0]);
314                     if (!value.first) return false;
315                     rr.mDelayTime = value.second;
316 
317                     // Check if optional randomization value present
318                     if (splitStr.length > 1) {
319                         splitStr[1] = splitStr[1].trim();
320                         if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
321                         value = parseNonNegativeInt("randomizationTime", splitStr[1]);
322                         if (!value.first) return false;
323                         rr.mRandomizationTime = value.second;
324                     } else {
325                         rr.mRandomizationTime = defaultRandomization;
326                     }
327                     mRetryArray.add(rr);
328                 }
329             }
330             if (mRetryArray.size() > mMaxRetryCount) {
331                 mMaxRetryCount = mRetryArray.size();
332                 if (VDBG) log("configure: setting mMaxRetryCount=" + mMaxRetryCount);
333             }
334         } else {
335             log("configure: cleared");
336         }
337 
338         if (VDBG) log("configure: true");
339         return true;
340     }
341 
342     /**
343      * Configure the retry manager
344      */
configureRetry()345     private void configureRetry() {
346         String configString = null;
347         String otherConfigString = null;
348 
349         try {
350             if (TelephonyUtils.IS_DEBUGGABLE) {
351                 // Using system properties is easier for testing from command line.
352                 String config = SystemProperties.get("test.data_retry_config");
353                 if (!TextUtils.isEmpty(config)) {
354                     configure(config);
355                     return;
356                 }
357             }
358 
359             CarrierConfigManager configManager = (CarrierConfigManager)
360                     mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
361             PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
362 
363             mInterApnDelay = b.getLong(
364                     CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG,
365                     DEFAULT_INTER_APN_DELAY);
366             mFailFastInterApnDelay = b.getLong(
367                     CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG,
368                     DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING);
369             mApnRetryAfterDisconnectDelay = b.getLong(
370                     CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG,
371                     DEFAULT_APN_RETRY_AFTER_DISCONNECT_DELAY);
372             mMaxSameApnRetry = b.getInt(
373                     CarrierConfigManager
374                             .KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT,
375                     DEFAULT_MAX_SAME_APN_RETRY);
376 
377             // Load all retry patterns for all different APNs.
378             String[] allConfigStrings = b.getStringArray(
379                     CarrierConfigManager.KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS);
380             if (allConfigStrings != null) {
381                 for (String s : allConfigStrings) {
382                     if (!TextUtils.isEmpty(s)) {
383                         String splitStr[] = s.split(":", 2);
384                         if (splitStr.length == 2) {
385                             String apnTypeStr = splitStr[0].trim();
386                             // Check if this retry pattern is for the APN we want.
387                             if (apnTypeStr.equals(ApnSetting.getApnTypeString(apnType))) {
388                                 // Extract the config string. Note that an empty string is valid
389                                 // here, meaning no retry for the specified APN.
390                                 configString = splitStr[1];
391                                 break;
392                             } else if (apnTypeStr.equals(OTHERS_APN_TYPE)) {
393                                 // Extract the config string. Note that an empty string is valid
394                                 // here, meaning no retry for all other APNs.
395                                 otherConfigString = splitStr[1];
396                             }
397                         }
398                     }
399                 }
400             }
401 
402             if (configString == null) {
403                 if (otherConfigString != null) {
404                     configString = otherConfigString;
405                 } else {
406                     // We should never reach here. If we reach here, it must be a configuration
407                     // error bug.
408                     log("Invalid APN retry configuration!. Use the default one now.");
409                     configString = DEFAULT_DATA_RETRY_CONFIG;
410                 }
411             }
412         } catch (NullPointerException ex) {
413             // We should never reach here unless there is a bug
414             log("Failed to read configuration! Use the hardcoded default value.");
415 
416             mInterApnDelay = DEFAULT_INTER_APN_DELAY;
417             mFailFastInterApnDelay = DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING;
418             configString = DEFAULT_DATA_RETRY_CONFIG;
419         }
420 
421         if (VDBG) {
422             log("mInterApnDelay = " + mInterApnDelay + ", mFailFastInterApnDelay = " +
423                     mFailFastInterApnDelay);
424         }
425 
426         configure(configString);
427     }
428 
429     /**
430      * Return the timer that should be used to trigger the data reconnection
431      */
432     @UnsupportedAppUsage
getRetryTimer()433     private long getRetryTimer() {
434         int index;
435         if (mRetryCount < mRetryArray.size()) {
436             index = mRetryCount;
437         } else {
438             index = mRetryArray.size() - 1;
439         }
440 
441         long retVal;
442         if ((index >= 0) && (index < mRetryArray.size())) {
443             retVal = mRetryArray.get(index).mDelayTime + nextRandomizationTime(index);
444         } else {
445             retVal = 0;
446         }
447 
448         if (DBG) log("getRetryTimer: " + retVal);
449         return retVal;
450     }
451 
452     /**
453      * Parse an integer validating the value is not negative.
454      * @param name Name
455      * @param stringValue Value
456      * @return Pair.first == true if stringValue an integer >= 0
457      */
parseNonNegativeInt(String name, String stringValue)458     private Pair<Boolean, Integer> parseNonNegativeInt(String name, String stringValue) {
459         int value;
460         Pair<Boolean, Integer> retVal;
461         try {
462             value = Integer.parseInt(stringValue);
463             retVal = new Pair<>(validateNonNegativeInt(name, value), value);
464         } catch (NumberFormatException e) {
465             Rlog.e(LOG_TAG, name + " bad value: " + stringValue, e);
466             retVal = new Pair<>(false, 0);
467         }
468         if (VDBG) {
469             log("parseNonNegativeInt: " + name + ", " + stringValue + ", "
470                     + retVal.first + ", " + retVal.second);
471         }
472         return retVal;
473     }
474 
475     /**
476      * Validate an integer is >= 0 and logs an error if not
477      * @param name Name
478      * @param value Value
479      * @return Pair.first
480      */
validateNonNegativeInt(String name, long value)481     private boolean validateNonNegativeInt(String name, long value) {
482         boolean retVal;
483         if (value < 0) {
484             Rlog.e(LOG_TAG, name + " bad value: is < 0");
485             retVal = false;
486         } else {
487             retVal = true;
488         }
489         if (VDBG) log("validateNonNegative: " + name + ", " + value + ", " + retVal);
490         return retVal;
491     }
492 
493     /**
494      * Return next random number for the index
495      * @param index Retry index
496      */
nextRandomizationTime(int index)497     private long nextRandomizationTime(int index) {
498         long randomTime = mRetryArray.get(index).mRandomizationTime;
499         if (randomTime == 0) {
500             return 0;
501         } else {
502             return mRng.nextInt((int) randomTime);
503         }
504     }
505 
getNetworkSuggestedRetryDelay()506     private long getNetworkSuggestedRetryDelay() {
507         long retryElapseTime = mDataThrottler.getRetryTime(apnType);
508         if (retryElapseTime == NO_RETRY || retryElapseTime == NO_SUGGESTED_RETRY_DELAY) {
509             return retryElapseTime;
510         }
511 
512         // The time from data throttler is system's elapsed time. We need to return the delta. If
513         // less than 0, then return 0 (i.e. retry immediately).
514         return Math.max(0, retryElapseTime - SystemClock.elapsedRealtime());
515     }
516 
517     /**
518      * Get the next APN setting for data call setup.
519      * @return APN setting to try. {@code null} if cannot find any APN,
520      */
getNextApnSetting()521     public @Nullable ApnSetting getNextApnSetting() {
522         if (mWaitingApns == null || mWaitingApns.size() == 0) {
523             log("Waiting APN list is null or empty.");
524             return null;
525         }
526 
527         long networkSuggestedRetryDelay = getNetworkSuggestedRetryDelay();
528         if (networkSuggestedRetryDelay == NO_RETRY) {
529             log("Network suggested no retry.");
530             return null;
531         }
532 
533         // If the network had suggested a retry delay, we should retry the current APN again
534         // (up to mMaxSameApnRetry times) instead of getting the next APN setting from
535         // our own list. If the APN waiting list has been reset before a setup data responses
536         // arrive (i.e. mCurrentApnIndex=-1), then ignore the network suggested retry.
537         if (mCurrentApnIndex != -1 && networkSuggestedRetryDelay != NO_SUGGESTED_RETRY_DELAY
538                 && mSameApnRetryCount < mMaxSameApnRetry) {
539             mSameApnRetryCount++;
540             return mWaitingApns.get(mCurrentApnIndex);
541         }
542 
543         mSameApnRetryCount = 0;
544 
545         int index = mCurrentApnIndex;
546         // Loop through the APN list to find out the index of next non-permanent failed APN.
547         while (true) {
548             if (++index == mWaitingApns.size()) index = 0;
549 
550             // Stop if we find the non-failed APN.
551             if (!mWaitingApns.get(index).getPermanentFailed()) {
552                 break;
553             }
554 
555             // If all APNs have permanently failed, bail out.
556             if (mWaitingApns.stream().allMatch(ApnSetting::getPermanentFailed)) {
557                 return null;
558             }
559         }
560 
561         mCurrentApnIndex = index;
562         return mWaitingApns.get(mCurrentApnIndex);
563     }
564 
565     /**
566      * Get the delay for trying the next waiting APN from the list.
567      * @param failFastEnabled True if fail fast mode enabled. In this case we'll use a shorter
568      *                        delay.
569      * @return delay in milliseconds
570      */
getDelayForNextApn(boolean failFastEnabled)571     public long getDelayForNextApn(boolean failFastEnabled) {
572 
573         if (mWaitingApns == null || mWaitingApns.size() == 0) {
574             log("Waiting APN list is null or empty.");
575             return NO_RETRY;
576         }
577 
578         long networkSuggestedDelay = getNetworkSuggestedRetryDelay();
579         log("Network suggested delay=" + networkSuggestedDelay + "ms");
580 
581         if (networkSuggestedDelay == NO_RETRY) {
582             log("Network suggested not retrying.");
583             return NO_RETRY;
584         }
585 
586         if (networkSuggestedDelay != NO_SUGGESTED_RETRY_DELAY
587                 && mSameApnRetryCount < mMaxSameApnRetry) {
588             // If the network explicitly suggests a retry delay, we should use it, even in fail fast
589             // mode.
590             log("Network suggested retry in " + networkSuggestedDelay + " ms.");
591             return networkSuggestedDelay;
592         }
593 
594         // In order to determine the delay to try next APN, we need to peek the next available APN.
595         // Case 1 - If we will start the next round of APN trying,
596         //    we use the exponential-growth delay. (e.g. 5s, 10s, 30s...etc.)
597         // Case 2 - If we are still within the same round of APN trying,
598         //    we use the fixed standard delay between APNs. (e.g. 20s)
599 
600         int index = mCurrentApnIndex;
601         while (true) {
602             if (++index >= mWaitingApns.size()) index = 0;
603 
604             // Stop if we find the non-failed APN.
605             if (!mWaitingApns.get(index).getPermanentFailed()) {
606                 break;
607             }
608 
609             // If all APNs have permanently failed, bail out.
610             if (mWaitingApns.stream().allMatch(ApnSetting::getPermanentFailed)) {
611                 log("All APNs have permanently failed.");
612                 return NO_RETRY;
613             }
614         }
615 
616         long delay;
617         if (index <= mCurrentApnIndex) {
618             // Case 1, if the next APN is in the next round.
619             if (!mRetryForever && mRetryCount + 1 > mMaxRetryCount) {
620                 log("Reached maximum retry count " + mMaxRetryCount + ".");
621                 return NO_RETRY;
622             }
623             delay = getRetryTimer();
624             ++mRetryCount;
625         } else {
626             // Case 2, if the next APN is still in the same round.
627             delay = mInterApnDelay;
628         }
629 
630         if (failFastEnabled && delay > mFailFastInterApnDelay) {
631             // If we enable fail fast mode, and the delay we got is longer than
632             // fail-fast delay (mFailFastInterApnDelay), use the fail-fast delay.
633             // If the delay we calculated is already shorter than fail-fast delay,
634             // then ignore fail-fast delay.
635             delay = mFailFastInterApnDelay;
636         }
637 
638         return delay;
639     }
640 
641     /**
642      * Mark the APN setting permanently failed.
643      * @param apn APN setting to be marked as permanently failed
644      * */
markApnPermanentFailed(ApnSetting apn)645     public void markApnPermanentFailed(ApnSetting apn) {
646         if (apn != null) {
647             apn.setPermanentFailed(true);
648         }
649     }
650 
651     /**
652      * Reset the retry manager.
653      */
reset()654     private void reset() {
655         mMaxRetryCount = 0;
656         mRetryCount = 0;
657         mCurrentApnIndex = -1;
658         mSameApnRetryCount = 0;
659         mRetryArray.clear();
660     }
661 
662     /**
663      * Set waiting APNs for retrying in case needed.
664      * @param waitingApns Waiting APN list
665      */
setWaitingApns(ArrayList<ApnSetting> waitingApns)666     public void setWaitingApns(ArrayList<ApnSetting> waitingApns) {
667 
668         if (waitingApns == null) {
669             log("No waiting APNs provided");
670             return;
671         }
672 
673         mWaitingApns = waitingApns;
674 
675         // Since we replace the entire waiting APN list, we need to re-config this retry manager.
676         configureRetry();
677 
678         for (ApnSetting apn : mWaitingApns) {
679             apn.setPermanentFailed(false);
680         }
681 
682         log("Setting " + mWaitingApns.size() + " waiting APNs.");
683 
684         if (VDBG) {
685             for (int i = 0; i < mWaitingApns.size(); i++) {
686                 log("  [" + i + "]:" + mWaitingApns.get(i));
687             }
688         }
689     }
690 
691     /**
692      * Get the list of waiting APNs.
693      * @return the list of waiting APNs
694      */
getWaitingApns()695     public @NonNull ArrayList<ApnSetting> getWaitingApns() {
696         return mWaitingApns;
697     }
698 
699     /**
700      * Get the delay in milliseconds for APN retry after disconnect
701      * @return The delay in milliseconds
702      */
getRetryAfterDisconnectDelay()703     public long getRetryAfterDisconnectDelay() {
704         return mApnRetryAfterDisconnectDelay;
705     }
706 
toString()707     public String toString() {
708         if (mConfig == null) return "";
709         return "RetryManager: apnType=" + ApnSetting.getApnTypeString(apnType)
710                 + " mRetryCount="
711                 + mRetryCount + " mMaxRetryCount=" + mMaxRetryCount + " mCurrentApnIndex="
712                 + mCurrentApnIndex + " mSameApnRtryCount=" + mSameApnRetryCount
713                 + " networkSuggestedDelay=" + getNetworkSuggestedRetryDelay() + " mRetryForever="
714                 + mRetryForever + " mInterApnDelay=" + mInterApnDelay
715                 + " mApnRetryAfterDisconnectDelay=" + mApnRetryAfterDisconnectDelay
716                 + " mConfig={" + mConfig + "}";
717     }
718 
719     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
log(String s)720     private void log(String s) {
721         Rlog.d(LOG_TAG, "[" + ApnSetting.getApnTypeString(apnType) + "] " + s);
722     }
723 }
724