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