• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
RetryRec(int delayTime, int randomizationTime)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 */
RetryManager()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      */
configure(int maxRetryCount, int retryTime, int randomizationTime)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 arbitary
157      * sequences of times. See class comments for the
158      * string format.
159      *
160      * @return true if successfull
161      */
configure(String configStr)162     public boolean configure(String configStr) {
163         if (DBG) log("configure: '" + configStr + "'");
164 
165         if (!TextUtils.isEmpty(configStr)) {
166             int defaultRandomization = 0;
167 
168             if (DBG) log("configure: not empty");
169 
170             mMaxRetryCount = 0;
171             resetRetryCount();
172             mRetryArray.clear();
173 
174             String strArray[] = configStr.split(",");
175             for (int i = 0; i < strArray.length; i++) {
176                 if (DBG) log("configure: strArray[" + i + "]='" + strArray[i] + "'");
177                 Pair<Boolean, Integer> value;
178                 String splitStr[] = strArray[i].split("=", 2);
179                 splitStr[0] = splitStr[0].trim();
180                 if (DBG) log("configure: splitStr[0]='" + splitStr[0] + "'");
181                 if (splitStr.length > 1) {
182                     splitStr[1] = splitStr[1].trim();
183                     if (DBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
184                     if (TextUtils.equals(splitStr[0], "default_randomization")) {
185                         value = parseNonNegativeInt(splitStr[0], splitStr[1]);
186                         if (!value.first) return false;
187                         defaultRandomization = value.second;
188                     } else if (TextUtils.equals(splitStr[0], "max_retries")) {
189                         if (TextUtils.equals("infinite",splitStr[1])) {
190                             mRetryForever = true;
191                         } else {
192                             value = parseNonNegativeInt(splitStr[0], splitStr[1]);
193                             if (!value.first) return false;
194                             mMaxRetryCount = value.second;
195                         }
196                     } else {
197                         Log.e(LOG_TAG, "Unrecognized configuration name value pair: "
198                                         + strArray[i]);
199                         return false;
200                     }
201                 } else {
202                     /**
203                      * Assume a retry time with an optional randomization value
204                      * following a ":"
205                      */
206                     splitStr = strArray[i].split(":", 2);
207                     splitStr[0] = splitStr[0].trim();
208                     RetryRec rr = new RetryRec(0, 0);
209                     value = parseNonNegativeInt("delayTime", splitStr[0]);
210                     if (!value.first) return false;
211                     rr.mDelayTime = value.second;
212 
213                     // Check if optional randomization value present
214                     if (splitStr.length > 1) {
215                         splitStr[1] = splitStr[1].trim();
216                         if (DBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
217                         value = parseNonNegativeInt("randomizationTime", splitStr[1]);
218                         if (!value.first) return false;
219                         rr.mRandomizationTime = value.second;
220                     } else {
221                         rr.mRandomizationTime = defaultRandomization;
222                     }
223                     mRetryArray.add(rr);
224                 }
225             }
226             if (mRetryArray.size() > mMaxRetryCount) {
227                 mMaxRetryCount = mRetryArray.size();
228                 if (DBG) log("configure: setting mMaxRetryCount=" + mMaxRetryCount);
229             }
230             if (DBG) log("configure: true");
231             return true;
232         } else {
233             if (DBG) log("configure: false it's empty");
234             return false;
235         }
236     }
237 
238     /**
239      * Report whether data reconnection should be retried
240      *
241      * @return {@code true} if the max retires has not been reached. {@code
242      *         false} otherwise.
243      */
isRetryNeeded()244     public boolean isRetryNeeded() {
245         boolean retVal = mRetryForever || (mRetryCount < mMaxRetryCount);
246         if (DBG) log("isRetryNeeded: " + retVal);
247         return retVal;
248     }
249 
250     /**
251      * Return the timer that should be used to trigger the data reconnection
252      */
getRetryTimer()253     public int getRetryTimer() {
254         int index;
255         if (mRetryCount < mRetryArray.size()) {
256             index = mRetryCount;
257         } else {
258             index = mRetryArray.size() - 1;
259         }
260 
261         int retVal;
262         if ((index >= 0) && (index < mRetryArray.size())) {
263             retVal = mRetryArray.get(index).mDelayTime + nextRandomizationTime(index);
264         } else {
265             retVal = 0;
266         }
267 
268         if (DBG) log("getRetryTimer: " + retVal);
269         return retVal;
270     }
271 
272     /**
273      * @return retry count
274      */
getRetryCount()275     public int getRetryCount() {
276         if (DBG) log("getRetryCount: " + mRetryCount);
277         return mRetryCount;
278     }
279 
280     /**
281      * Increase the retry counter, does not change retry forever.
282      */
increaseRetryCount()283     public void increaseRetryCount() {
284         mRetryCount++;
285         if (mRetryCount > mMaxRetryCount) {
286             mRetryCount = mMaxRetryCount;
287         }
288         if (DBG) log("increseRetryCount: " + mRetryCount);
289     }
290 
291     /**
292      * Set retry count to the specified value
293      * and turns off retrying forever.
294      */
setRetryCount(int count)295     public void setRetryCount(int count) {
296         mRetryCount = count;
297         if (mRetryCount > mMaxRetryCount) {
298             mRetryCount = mMaxRetryCount;
299         }
300 
301         if (mRetryCount < 0) {
302             mRetryCount = 0;
303         }
304 
305         mRetryForever = false;
306         if (DBG) log("setRetryCount: " + mRetryCount);
307     }
308 
309     /**
310      * Reset network re-registration indicator and clear the data-retry counter
311      * and turns off retrying forever.
312      */
resetRetryCount()313     public void resetRetryCount() {
314         mRetryCount = 0;
315         mRetryForever = false;
316         if (DBG) log("resetRetryCount: " + mRetryCount);
317     }
318 
319     /**
320      * Retry forever using last timeout time.
321      */
retryForeverUsingLastTimeout()322     public void retryForeverUsingLastTimeout() {
323         mRetryCount = mMaxRetryCount;
324         mRetryForever = true;
325         if (DBG) log("retryForeverUsingLastTimeout: " + mRetryForever + ", " + mRetryCount);
326     }
327 
328     /**
329      * @return true if retrying forever
330      */
isRetryForever()331     public boolean isRetryForever() {
332         if (DBG) log("isRetryForever: " + mRetryForever);
333         return mRetryForever;
334     }
335 
336     /**
337      * Parse an integer validating the value is not negative.
338      *
339      * @param name
340      * @param stringValue
341      * @return Pair.first == true if stringValue an integer >= 0
342      */
parseNonNegativeInt(String name, String stringValue)343     private Pair<Boolean, Integer> parseNonNegativeInt(String name, String stringValue) {
344         int value;
345         Pair<Boolean, Integer> retVal;
346         try {
347             value = Integer.parseInt(stringValue);
348             retVal = new Pair<Boolean, Integer>(validateNonNegativeInt(name, value), value);
349         } catch (NumberFormatException e) {
350             Log.e(LOG_TAG, name + " bad value: " + stringValue, e);
351             retVal = new Pair<Boolean, Integer>(false, 0);
352         }
353         if (DBG) log("parseNonNetativeInt: " + name + ", " + stringValue + ", "
354                     + retVal.first + ", " + retVal.second);
355         return retVal;
356     }
357 
358     /**
359      * Validate an integer is >= 0 and logs an error if not
360      *
361      * @param name
362      * @param value
363      * @return Pair.first
364      */
validateNonNegativeInt(String name, int value)365     private boolean validateNonNegativeInt(String name, int value) {
366         boolean retVal;
367         if (value < 0) {
368             Log.e(LOG_TAG, name + " bad value: is < 0");
369             retVal = false;
370         } else {
371             retVal = true;
372         }
373         if (DBG) log("validateNonNegative: " + name + ", " + value + ", " + retVal);
374         return retVal;
375     }
376 
377     /**
378      * Return next random number for the index
379      */
nextRandomizationTime(int index)380     private int nextRandomizationTime(int index) {
381         int randomTime = mRetryArray.get(index).mRandomizationTime;
382         if (randomTime == 0) {
383             return 0;
384         } else {
385             return rng.nextInt(randomTime);
386         }
387     }
388 
log(String s)389     private void log(String s) {
390         Log.d(LOG_TAG, s);
391     }
392 }
393