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