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.util.Log;
     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  * retires 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 retires with no randomization value which means its 0:
     58  * <ul><li><code>"1000, 2000, 3000"</code></ul>
     59  *
     60  * <li>10 retires 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 retires 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 retires 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 int RETRYIES_NOT_STARTED = 0;
     79     static public final int RETRYIES_ON_GOING = 1;
     80     static public final int RETRYIES_COMPLETED = 2;
     81 
     82     /**
     83      * Retry record with times in milli-seconds
     84      */
     85     private static class RetryRec {
     86         RetryRec(int delayTime, int randomizationTime) {
     87             mDelayTime = delayTime;
     88             mRandomizationTime = randomizationTime;
     89         }
     90 
     91         int mDelayTime;
     92         int mRandomizationTime;
     93     }
     94 
     95     /** The array of retry records */
     96     private ArrayList<RetryRec> mRetryArray = new ArrayList<RetryRec>();
     97 
     98     /** When true isRetryNeeded() will always return true */
     99     private boolean mRetryForever;
    100 
    101     /**
    102      * The maximum number of retries to attempt before
    103      * isRetryNeeded returns false
    104      */
    105     private int mMaxRetryCount;
    106 
    107     /** The current number of retires */
    108     private int mRetryCount;
    109 
    110     /** Random number generator */
    111     private Random rng = new Random();
    112 
    113     /** Constructor */
    114     public RetryManager() {
    115         if (DBG) log("constructor");
    116     }
    117 
    118     /**
    119      * Configure for a simple linear sequence of times plus
    120      * a random value.
    121      *
    122      * @param maxRetryCount is the maximum number of retries
    123      *        before isRetryNeeded returns false.
    124      * @param retryTime is a time that will be returned by getRetryTime.
    125      * @param randomizationTime a random value between 0 and
    126      *        randomizationTime will be added to retryTime. this
    127      *        parameter may be 0.
    128      * @return true if successfull
    129      */
    130     public boolean configure(int maxRetryCount, int retryTime, int randomizationTime) {
    131         Pair<Boolean, Integer> value;
    132 
    133         if (DBG) log("configure: " + maxRetryCount + ", " + retryTime + "," + randomizationTime);
    134 
    135         if (!validateNonNegativeInt("maxRetryCount", maxRetryCount)) {
    136             return false;
    137         }
    138 
    139         if (!validateNonNegativeInt("retryTime", retryTime)) {
    140             return false;
    141         }
    142 
    143         if (!validateNonNegativeInt("randomizationTime", randomizationTime)) {
    144             return false;
    145         }
    146 
    147         mMaxRetryCount = maxRetryCount;
    148         resetRetryCount();
    149         mRetryArray.clear();
    150         mRetryArray.add(new RetryRec(retryTime, randomizationTime));
    151 
    152         return true;
    153     }
    154 
    155     /**
    156      * Configure for using string which allow arbitrary
    157      * sequences of times. See class comments for the
    158      * string format.
    159      *
    160      * @return true if successful
    161      */
    162     public boolean configure(String configStr) {
    163         // Strip quotes if present.
    164         if ((configStr.startsWith("\"") && configStr.endsWith("\""))) {
    165             configStr = configStr.substring(1, configStr.length()-1);
    166         }
    167         if (DBG) log("configure: '" + configStr + "'");
    168 
    169         if (!TextUtils.isEmpty(configStr)) {
    170             int defaultRandomization = 0;
    171 
    172             if (DBG) log("configure: not empty");
    173 
    174             mMaxRetryCount = 0;
    175             resetRetryCount();
    176             mRetryArray.clear();
    177 
    178             String strArray[] = configStr.split(",");
    179             for (int i = 0; i < strArray.length; i++) {
    180                 if (DBG) log("configure: strArray[" + i + "]='" + strArray[i] + "'");
    181                 Pair<Boolean, Integer> value;
    182                 String splitStr[] = strArray[i].split("=", 2);
    183                 splitStr[0] = splitStr[0].trim();
    184                 if (DBG) log("configure: splitStr[0]='" + splitStr[0] + "'");
    185                 if (splitStr.length > 1) {
    186                     splitStr[1] = splitStr[1].trim();
    187                     if (DBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
    188                     if (TextUtils.equals(splitStr[0], "default_randomization")) {
    189                         value = parseNonNegativeInt(splitStr[0], splitStr[1]);
    190                         if (!value.first) return false;
    191                         defaultRandomization = value.second;
    192                     } else if (TextUtils.equals(splitStr[0], "max_retries")) {
    193                         if (TextUtils.equals("infinite",splitStr[1])) {
    194                             mRetryForever = true;
    195                         } else {
    196                             value = parseNonNegativeInt(splitStr[0], splitStr[1]);
    197                             if (!value.first) return false;
    198                             mMaxRetryCount = value.second;
    199                         }
    200                     } else {
    201                         Log.e(LOG_TAG, "Unrecognized configuration name value pair: "
    202                                         + strArray[i]);
    203                         return false;
    204                     }
    205                 } else {
    206                     /**
    207                      * Assume a retry time with an optional randomization value
    208                      * following a ":"
    209                      */
    210                     splitStr = strArray[i].split(":", 2);
    211                     splitStr[0] = splitStr[0].trim();
    212                     RetryRec rr = new RetryRec(0, 0);
    213                     value = parseNonNegativeInt("delayTime", splitStr[0]);
    214                     if (!value.first) return false;
    215                     rr.mDelayTime = value.second;
    216 
    217                     // Check if optional randomization value present
    218                     if (splitStr.length > 1) {
    219                         splitStr[1] = splitStr[1].trim();
    220                         if (DBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
    221                         value = parseNonNegativeInt("randomizationTime", splitStr[1]);
    222                         if (!value.first) return false;
    223                         rr.mRandomizationTime = value.second;
    224                     } else {
    225                         rr.mRandomizationTime = defaultRandomization;
    226                     }
    227                     mRetryArray.add(rr);
    228                 }
    229             }
    230             if (mRetryArray.size() > mMaxRetryCount) {
    231                 mMaxRetryCount = mRetryArray.size();
    232                 if (DBG) log("configure: setting mMaxRetryCount=" + mMaxRetryCount);
    233             }
    234             if (DBG) log("configure: true");
    235             return true;
    236         } else {
    237             if (DBG) log("configure: false it's empty");
    238             return false;
    239         }
    240     }
    241 
    242     /**
    243      * Report whether data reconnection should be retried
    244      *
    245      * @return {@code true} if the max retires has not been reached. {@code
    246      *         false} otherwise.
    247      */
    248     public boolean isRetryNeeded() {
    249         boolean retVal = mRetryForever || (mRetryCount < mMaxRetryCount);
    250         if (DBG) log("isRetryNeeded: " + retVal);
    251         return retVal;
    252     }
    253 
    254     /**
    255      * Return the timer that should be used to trigger the data reconnection
    256      */
    257     public int getRetryTimer() {
    258         int index;
    259         if (mRetryCount < mRetryArray.size()) {
    260             index = mRetryCount;
    261         } else {
    262             index = mRetryArray.size() - 1;
    263         }
    264 
    265         int retVal;
    266         if ((index >= 0) && (index < mRetryArray.size())) {
    267             retVal = mRetryArray.get(index).mDelayTime + nextRandomizationTime(index);
    268         } else {
    269             retVal = 0;
    270         }
    271 
    272         if (DBG) log("getRetryTimer: " + retVal);
    273         return retVal;
    274     }
    275 
    276     /**
    277      * @return retry count
    278      */
    279     public int getRetryCount() {
    280         if (DBG) log("getRetryCount: " + mRetryCount);
    281         return mRetryCount;
    282     }
    283 
    284     /**
    285      * Increase the retry counter, does not change retry forever.
    286      */
    287     public void increaseRetryCount() {
    288         mRetryCount++;
    289         if (mRetryCount > mMaxRetryCount) {
    290             mRetryCount = mMaxRetryCount;
    291         }
    292         if (DBG) log("increseRetryCount: " + mRetryCount);
    293     }
    294 
    295     /**
    296      * Set retry count to the specified value
    297      * and turns off retrying forever.
    298      */
    299     public void setRetryCount(int count) {
    300         mRetryCount = count;
    301         if (mRetryCount > mMaxRetryCount) {
    302             mRetryCount = mMaxRetryCount;
    303         }
    304 
    305         if (mRetryCount < 0) {
    306             mRetryCount = 0;
    307         }
    308 
    309         mRetryForever = false;
    310         if (DBG) log("setRetryCount: " + mRetryCount);
    311     }
    312 
    313     /**
    314      * Reset network re-registration indicator and clear the data-retry counter
    315      * and turns off retrying forever.
    316      */
    317     public void resetRetryCount() {
    318         mRetryCount = 0;
    319         mRetryForever = false;
    320         if (DBG) log("resetRetryCount: " + mRetryCount);
    321     }
    322 
    323     /**
    324      * Retry forever using last timeout time.
    325      */
    326     public void retryForeverUsingLastTimeout() {
    327         mRetryCount = mMaxRetryCount;
    328         mRetryForever = true;
    329         if (DBG) log("retryForeverUsingLastTimeout: " + mRetryForever + ", " + mRetryCount);
    330     }
    331 
    332     /**
    333      * @return true if retrying forever
    334      */
    335     public boolean isRetryForever() {
    336         if (DBG) log("isRetryForever: " + mRetryForever);
    337         return mRetryForever;
    338     }
    339 
    340     /**
    341      * Parse an integer validating the value is not negative.
    342      *
    343      * @param name
    344      * @param stringValue
    345      * @return Pair.first == true if stringValue an integer >= 0
    346      */
    347     private Pair<Boolean, Integer> parseNonNegativeInt(String name, String stringValue) {
    348         int value;
    349         Pair<Boolean, Integer> retVal;
    350         try {
    351             value = Integer.parseInt(stringValue);
    352             retVal = new Pair<Boolean, Integer>(validateNonNegativeInt(name, value), value);
    353         } catch (NumberFormatException e) {
    354             Log.e(LOG_TAG, name + " bad value: " + stringValue, e);
    355             retVal = new Pair<Boolean, Integer>(false, 0);
    356         }
    357         if (DBG) log("parseNonNetativeInt: " + name + ", " + stringValue + ", "
    358                     + retVal.first + ", " + retVal.second);
    359         return retVal;
    360     }
    361 
    362     /**
    363      * Validate an integer is >= 0 and logs an error if not
    364      *
    365      * @param name
    366      * @param value
    367      * @return Pair.first
    368      */
    369     private boolean validateNonNegativeInt(String name, int value) {
    370         boolean retVal;
    371         if (value < 0) {
    372             Log.e(LOG_TAG, name + " bad value: is < 0");
    373             retVal = false;
    374         } else {
    375             retVal = true;
    376         }
    377         if (DBG) log("validateNonNegative: " + name + ", " + value + ", " + retVal);
    378         return retVal;
    379     }
    380 
    381     /**
    382      * Return next random number for the index
    383      */
    384     private int nextRandomizationTime(int index) {
    385         int randomTime = mRetryArray.get(index).mRandomizationTime;
    386         if (randomTime == 0) {
    387             return 0;
    388         } else {
    389             return rng.nextInt(randomTime);
    390         }
    391     }
    392 
    393     private void log(String s) {
    394         Log.d(LOG_TAG, s);
    395     }
    396 }
    397