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