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
17package com.android.internal.telephony;
18
19import android.telephony.Rlog;
20import android.util.Pair;
21import android.text.TextUtils;
22
23import java.util.Random;
24import 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 */
75public 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 {
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 */
116    public RetryManager() {
117        if (VDBG) log("constructor");
118    }
119
120    @Override
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
367    public void resetRetryCount() {
368        mRetryCount = 0;
369        if (DBG) log("resetRetryCount: " + mRetryCount);
370    }
371
372    /**
373     * Retry forever using last timeout time.
374     */
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     */
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     */
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     */
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     */
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
442    private void log(String s) {
443        Rlog.d(LOG_TAG, "[RM] " + s);
444    }
445}
446