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