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.telephony.Rlog;
     20 import android.util.Pair;
     21 import android.text.TextUtils;
     22 
     23 import java.util.Random;
     24 import java.util.ArrayList;
     25 
     26 /**
     27  * Retry manager allows a simple way to declare a series of
     28  * retry timeouts. After creating a RetryManager the configure
     29  * method is used to define the sequence. A simple linear series
     30  * may be initialized using configure with three integer parameters
     31  * The other configure method allows a series to be declared using
     32  * a string.
     33  *<p>
     34  * The format of the configuration string is a series of parameters
     35  * separated by a comma. There are two name value pair parameters plus a series
     36  * of delay times. The units of of these delay times is unspecified.
     37  * The name value pairs which may be specified are:
     38  *<ul>
     39  *<li>max_retries=<value>
     40  *<li>default_randomizationTime=<value>
     41  *</ul>
     42  *<p>
     43  * max_retries is the number of times that incrementRetryCount
     44  * maybe called before isRetryNeeded will return false. if value
     45  * is infinite then isRetryNeeded will always return true.
     46  *
     47  * default_randomizationTime will be used as the randomizationTime
     48  * for delay times which have no supplied randomizationTime. If
     49  * default_randomizationTime is not defined it defaults to 0.
     50  *<p>
     51  * The other parameters define The series of delay times and each
     52  * may have an optional randomization value separated from the
     53  * delay time by a colon.
     54  *<p>
     55  * Examples:
     56  * <ul>
     57  * <li>3 retries with no randomization value which means its 0:
     58  * <ul><li><code>"1000, 2000, 3000"</code></ul>
     59  *
     60  * <li>10 retries with a 500 default randomization value for each and
     61  * the 4..10 retries all using 3000 as the delay:
     62  * <ul><li><code>"max_retries=10, default_randomization=500, 1000, 2000, 3000"</code></ul>
     63  *
     64  * <li>4 retries with a 100 as the default randomization value for the first 2 values and
     65  * the other two having specified values of 500:
     66  * <ul><li><code>"default_randomization=100, 1000, 2000, 4000:500, 5000:500"</code></ul>
     67  *
     68  * <li>Infinite number of retries with the first one at 1000, the second at 2000 all
     69  * others will be at 3000.
     70  * <ul><li><code>"max_retries=infinite,1000,2000,3000</code></ul>
     71  * </ul>
     72  *
     73  * {@hide}
     74  */
     75 public class RetryManager {
     76     static public final String LOG_TAG = "RetryManager";
     77     static public final boolean DBG = false;
     78     static public final boolean VDBG = false;
     79 
     80     /**
     81      * Retry record with times in milli-seconds
     82      */
     83     private static class RetryRec {
     84         RetryRec(int delayTime, int randomizationTime) {
     85             mDelayTime = delayTime;
     86             mRandomizationTime = randomizationTime;
     87         }
     88 
     89         int mDelayTime;
     90         int mRandomizationTime;
     91     }
     92 
     93     /** The array of retry records */
     94     private ArrayList<RetryRec> mRetryArray = new ArrayList<RetryRec>();
     95 
     96     /** When true isRetryNeeded() will always return true */
     97     private boolean mRetryForever;
     98 
     99     /**
    100      * The maximum number of retries to attempt before
    101      * isRetryNeeded returns false
    102      */
    103     private int mMaxRetryCount;
    104 
    105     private int mCurMaxRetryCount;
    106 
    107     /** The current number of retries */
    108     private int mRetryCount;
    109 
    110     /** Random number generator */
    111     private Random mRng = new Random();
    112 
    113     private String mConfig;
    114 
    115     /** Constructor */
    116     public RetryManager() {
    117         if (VDBG) log("constructor");
    118     }
    119 
    120     @Override
    121     public String toString() {
    122         String ret = "RetryManager: { forever=" + mRetryForever + " maxRetry=" + mMaxRetryCount
    123                 + " curMaxRetry=" + mCurMaxRetryCount + " retry=" + mRetryCount
    124                 + " config={" + mConfig + "} retryArray={";
    125         for (RetryRec r : mRetryArray) {
    126             ret += r.mDelayTime + ":" + r.mRandomizationTime + " ";
    127         }
    128         ret += "}}";
    129         return ret;
    130     }
    131 
    132     /**
    133      * Configure for a simple linear sequence of times plus
    134      * a random value.
    135      *
    136      * @param maxRetryCount is the maximum number of retries
    137      *        before isRetryNeeded returns false.
    138      * @param retryTime is a time that will be returned by getRetryTime.
    139      * @param randomizationTime a random value between 0 and
    140      *        randomizationTime will be added to retryTime. this
    141      *        parameter may be 0.
    142      * @return true if successful
    143      */
    144     public boolean configure(int maxRetryCount, int retryTime, int randomizationTime) {
    145         Pair<Boolean, Integer> value;
    146 
    147         if (VDBG) log("configure: " + maxRetryCount + ", " + retryTime + "," + randomizationTime);
    148 
    149         if (!validateNonNegativeInt("maxRetryCount", maxRetryCount)) {
    150             return false;
    151         }
    152 
    153         if (!validateNonNegativeInt("retryTime", retryTime)) {
    154             return false;
    155         }
    156 
    157         if (!validateNonNegativeInt("randomizationTime", randomizationTime)) {
    158             return false;
    159         }
    160 
    161         mMaxRetryCount = maxRetryCount;
    162         mCurMaxRetryCount = mMaxRetryCount;
    163 
    164         resetRetryCount();
    165         mRetryArray.clear();
    166         mRetryArray.add(new RetryRec(retryTime, randomizationTime));
    167 
    168         return true;
    169     }
    170 
    171     /**
    172      * Configure for using string which allow arbitrary
    173      * sequences of times. See class comments for the
    174      * string format.
    175      *
    176      * @return true if successful
    177      */
    178     public boolean configure(String configStr) {
    179         // Strip quotes if present.
    180         if ((configStr.startsWith("\"") && configStr.endsWith("\""))) {
    181             configStr = configStr.substring(1, configStr.length()-1);
    182         }
    183         if (VDBG) log("configure: '" + configStr + "'");
    184         mConfig = configStr;
    185 
    186         if (!TextUtils.isEmpty(configStr)) {
    187             int defaultRandomization = 0;
    188 
    189             if (VDBG) log("configure: not empty");
    190 
    191             mMaxRetryCount = 0;
    192             resetRetryCount();
    193             mRetryArray.clear();
    194 
    195             String strArray[] = configStr.split(",");
    196             for (int i = 0; i < strArray.length; i++) {
    197                 if (VDBG) log("configure: strArray[" + i + "]='" + strArray[i] + "'");
    198                 Pair<Boolean, Integer> value;
    199                 String splitStr[] = strArray[i].split("=", 2);
    200                 splitStr[0] = splitStr[0].trim();
    201                 if (VDBG) log("configure: splitStr[0]='" + splitStr[0] + "'");
    202                 if (splitStr.length > 1) {
    203                     splitStr[1] = splitStr[1].trim();
    204                     if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
    205                     if (TextUtils.equals(splitStr[0], "default_randomization")) {
    206                         value = parseNonNegativeInt(splitStr[0], splitStr[1]);
    207                         if (!value.first) return false;
    208                         defaultRandomization = value.second;
    209                     } else if (TextUtils.equals(splitStr[0], "max_retries")) {
    210                         if (TextUtils.equals("infinite",splitStr[1])) {
    211                             mRetryForever = true;
    212                         } else {
    213                             value = parseNonNegativeInt(splitStr[0], splitStr[1]);
    214                             if (!value.first) return false;
    215                             mMaxRetryCount = value.second;
    216                         }
    217                     } else {
    218                         Rlog.e(LOG_TAG, "Unrecognized configuration name value pair: "
    219                                         + strArray[i]);
    220                         return false;
    221                     }
    222                 } else {
    223                     /**
    224                      * Assume a retry time with an optional randomization value
    225                      * following a ":"
    226                      */
    227                     splitStr = strArray[i].split(":", 2);
    228                     splitStr[0] = splitStr[0].trim();
    229                     RetryRec rr = new RetryRec(0, 0);
    230                     value = parseNonNegativeInt("delayTime", splitStr[0]);
    231                     if (!value.first) return false;
    232                     rr.mDelayTime = value.second;
    233 
    234                     // Check if optional randomization value present
    235                     if (splitStr.length > 1) {
    236                         splitStr[1] = splitStr[1].trim();
    237                         if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
    238                         value = parseNonNegativeInt("randomizationTime", splitStr[1]);
    239                         if (!value.first) return false;
    240                         rr.mRandomizationTime = value.second;
    241                     } else {
    242                         rr.mRandomizationTime = defaultRandomization;
    243                     }
    244                     mRetryArray.add(rr);
    245                 }
    246             }
    247             if (mRetryArray.size() > mMaxRetryCount) {
    248                 mMaxRetryCount = mRetryArray.size();
    249                 if (VDBG) log("configure: setting mMaxRetryCount=" + mMaxRetryCount);
    250             }
    251             mCurMaxRetryCount = mMaxRetryCount;
    252             if (VDBG) log("configure: true");
    253             return true;
    254         } else {
    255             if (VDBG) log("configure: false it's empty");
    256             return false;
    257         }
    258     }
    259 
    260     /**
    261      * Report whether data reconnection should be retried
    262      *
    263      * @return {@code true} if the max retries has not been reached. {@code
    264      *         false} otherwise.
    265      */
    266     public boolean isRetryNeeded() {
    267         boolean retVal = mRetryForever || (mRetryCount < mCurMaxRetryCount);
    268         if (DBG) log("isRetryNeeded: " + retVal);
    269         return retVal;
    270     }
    271 
    272     /**
    273      * Return the timer that should be used to trigger the data reconnection
    274      */
    275     public int getRetryTimer() {
    276         int index;
    277         if (mRetryCount < mRetryArray.size()) {
    278             index = mRetryCount;
    279         } else {
    280             index = mRetryArray.size() - 1;
    281         }
    282 
    283         int retVal;
    284         if ((index >= 0) && (index < mRetryArray.size())) {
    285             retVal = mRetryArray.get(index).mDelayTime + nextRandomizationTime(index);
    286         } else {
    287             retVal = 0;
    288         }
    289 
    290         if (DBG) log("getRetryTimer: " + retVal);
    291         return retVal;
    292     }
    293 
    294     /**
    295      * @return retry count
    296      */
    297     public int getRetryCount() {
    298         if (DBG) log("getRetryCount: " + mRetryCount);
    299         return mRetryCount;
    300     }
    301 
    302     /**
    303      * Increase the retry counter, does not change retry forever.
    304      */
    305     public void increaseRetryCount() {
    306         mRetryCount++;
    307         if (mRetryCount > mCurMaxRetryCount) {
    308             mRetryCount = mCurMaxRetryCount;
    309         }
    310         if (DBG) log("increaseRetryCount: " + mRetryCount);
    311     }
    312 
    313     /**
    314      * Set retry count to the specified value
    315      */
    316     public void setRetryCount(int count) {
    317         mRetryCount = count;
    318         if (mRetryCount > mCurMaxRetryCount) {
    319             mRetryCount = mCurMaxRetryCount;
    320         }
    321 
    322         if (mRetryCount < 0) {
    323             mRetryCount = 0;
    324         }
    325 
    326         if (DBG) log("setRetryCount: " + mRetryCount);
    327     }
    328 
    329     /**
    330      * Set current maximum retry count to the specified value
    331      */
    332     public void setCurMaxRetryCount(int count) {
    333         mCurMaxRetryCount = count;
    334 
    335         // Make sure it's not negative
    336         if (mCurMaxRetryCount < 0) {
    337             mCurMaxRetryCount = 0;
    338         }
    339 
    340         // Make sure mRetryCount is within range
    341         setRetryCount(mRetryCount);
    342 
    343         if (DBG) log("setCurMaxRetryCount: " + mCurMaxRetryCount);
    344     }
    345 
    346     /**
    347      * Restore CurMaxRetryCount
    348      */
    349     public void restoreCurMaxRetryCount() {
    350         mCurMaxRetryCount = mMaxRetryCount;
    351 
    352         // Make sure mRetryCount is within range
    353         setRetryCount(mRetryCount);
    354     }
    355 
    356     /**
    357      * Set retry forever to the specified value
    358      */
    359     public void setRetryForever(boolean retryForever) {
    360         mRetryForever = retryForever;
    361         if (DBG) log("setRetryForever: " + mRetryForever);
    362     }
    363 
    364     /**
    365      * Clear the data-retry counter
    366      */
    367     public void resetRetryCount() {
    368         mRetryCount = 0;
    369         if (DBG) log("resetRetryCount: " + mRetryCount);
    370     }
    371 
    372     /**
    373      * Retry forever using last timeout time.
    374      */
    375     public void retryForeverUsingLastTimeout() {
    376         mRetryCount = mCurMaxRetryCount;
    377         mRetryForever = true;
    378         if (DBG) log("retryForeverUsingLastTimeout: " + mRetryForever + ", " + mRetryCount);
    379     }
    380 
    381     /**
    382      * @return true if retrying forever
    383      */
    384     public boolean isRetryForever() {
    385         if (DBG) log("isRetryForever: " + mRetryForever);
    386         return mRetryForever;
    387     }
    388 
    389     /**
    390      * Parse an integer validating the value is not negative.
    391      *
    392      * @param name
    393      * @param stringValue
    394      * @return Pair.first == true if stringValue an integer >= 0
    395      */
    396     private Pair<Boolean, Integer> parseNonNegativeInt(String name, String stringValue) {
    397         int value;
    398         Pair<Boolean, Integer> retVal;
    399         try {
    400             value = Integer.parseInt(stringValue);
    401             retVal = new Pair<Boolean, Integer>(validateNonNegativeInt(name, value), value);
    402         } catch (NumberFormatException e) {
    403             Rlog.e(LOG_TAG, name + " bad value: " + stringValue, e);
    404             retVal = new Pair<Boolean, Integer>(false, 0);
    405         }
    406         if (VDBG) log("parseNonNetativeInt: " + name + ", " + stringValue + ", "
    407                     + retVal.first + ", " + retVal.second);
    408         return retVal;
    409     }
    410 
    411     /**
    412      * Validate an integer is >= 0 and logs an error if not
    413      *
    414      * @param name
    415      * @param value
    416      * @return Pair.first
    417      */
    418     private boolean validateNonNegativeInt(String name, int value) {
    419         boolean retVal;
    420         if (value < 0) {
    421             Rlog.e(LOG_TAG, name + " bad value: is < 0");
    422             retVal = false;
    423         } else {
    424             retVal = true;
    425         }
    426         if (VDBG) log("validateNonNegative: " + name + ", " + value + ", " + retVal);
    427         return retVal;
    428     }
    429 
    430     /**
    431      * Return next random number for the index
    432      */
    433     private int nextRandomizationTime(int index) {
    434         int randomTime = mRetryArray.get(index).mRandomizationTime;
    435         if (randomTime == 0) {
    436             return 0;
    437         } else {
    438             return mRng.nextInt(randomTime);
    439         }
    440     }
    441 
    442     private void log(String s) {
    443         Rlog.d(LOG_TAG, "[RM] " + s);
    444     }
    445 }
    446