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