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 {
RetryRec(int delayTime, int randomizationTime)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 */
RetryManager()116     public RetryManager() {
117         if (VDBG) log("constructor");
118     }
119 
120     @Override
toString()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      */
configure(int maxRetryCount, int retryTime, int randomizationTime)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      */
configure(String configStr)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      */
isRetryNeeded()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      */
getRetryTimer()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      */
getRetryCount()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      */
increaseRetryCount()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      */
setRetryCount(int count)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      */
setCurMaxRetryCount(int count)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      */
restoreCurMaxRetryCount()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      */
setRetryForever(boolean retryForever)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      */
resetRetryCount()367     public void resetRetryCount() {
368         mRetryCount = 0;
369         if (DBG) log("resetRetryCount: " + mRetryCount);
370     }
371 
372     /**
373      * Retry forever using last timeout time.
374      */
retryForeverUsingLastTimeout()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      */
isRetryForever()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      */
parseNonNegativeInt(String name, String stringValue)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      */
validateNonNegativeInt(String name, int value)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      */
nextRandomizationTime(int index)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 
log(String s)442     private void log(String s) {
443         Rlog.d(LOG_TAG, "[RM] " + s);
444     }
445 }
446