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