| /** |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.telephony; |
| |
| import android.telephony.Rlog; |
| import android.util.Pair; |
| import android.text.TextUtils; |
| |
| import java.util.Random; |
| import java.util.ArrayList; |
| |
| /** |
| * Retry manager allows a simple way to declare a series of |
| * retry timeouts. After creating a RetryManager the configure |
| * method is used to define the sequence. A simple linear series |
| * may be initialized using configure with three integer parameters |
| * The other configure method allows a series to be declared using |
| * a string. |
| *<p> |
| * The format of the configuration string is a series of parameters |
| * separated by a comma. There are two name value pair parameters plus a series |
| * of delay times. The units of of these delay times is unspecified. |
| * The name value pairs which may be specified are: |
| *<ul> |
| *<li>max_retries=<value> |
| *<li>default_randomizationTime=<value> |
| *</ul> |
| *<p> |
| * max_retries is the number of times that incrementRetryCount |
| * maybe called before isRetryNeeded will return false. if value |
| * is infinite then isRetryNeeded will always return true. |
| * |
| * default_randomizationTime will be used as the randomizationTime |
| * for delay times which have no supplied randomizationTime. If |
| * default_randomizationTime is not defined it defaults to 0. |
| *<p> |
| * The other parameters define The series of delay times and each |
| * may have an optional randomization value separated from the |
| * delay time by a colon. |
| *<p> |
| * Examples: |
| * <ul> |
| * <li>3 retries with no randomization value which means its 0: |
| * <ul><li><code>"1000, 2000, 3000"</code></ul> |
| * |
| * <li>10 retries with a 500 default randomization value for each and |
| * the 4..10 retries all using 3000 as the delay: |
| * <ul><li><code>"max_retries=10, default_randomization=500, 1000, 2000, 3000"</code></ul> |
| * |
| * <li>4 retries with a 100 as the default randomization value for the first 2 values and |
| * the other two having specified values of 500: |
| * <ul><li><code>"default_randomization=100, 1000, 2000, 4000:500, 5000:500"</code></ul> |
| * |
| * <li>Infinite number of retries with the first one at 1000, the second at 2000 all |
| * others will be at 3000. |
| * <ul><li><code>"max_retries=infinite,1000,2000,3000</code></ul> |
| * </ul> |
| * |
| * {@hide} |
| */ |
| public class RetryManager { |
| static public final String LOG_TAG = "RetryManager"; |
| static public final boolean DBG = false; |
| static public final boolean VDBG = false; |
| |
| /** |
| * Retry record with times in milli-seconds |
| */ |
| private static class RetryRec { |
| RetryRec(int delayTime, int randomizationTime) { |
| mDelayTime = delayTime; |
| mRandomizationTime = randomizationTime; |
| } |
| |
| int mDelayTime; |
| int mRandomizationTime; |
| } |
| |
| /** The array of retry records */ |
| private ArrayList<RetryRec> mRetryArray = new ArrayList<RetryRec>(); |
| |
| /** When true isRetryNeeded() will always return true */ |
| private boolean mRetryForever; |
| |
| /** |
| * The maximum number of retries to attempt before |
| * isRetryNeeded returns false |
| */ |
| private int mMaxRetryCount; |
| |
| private int mCurMaxRetryCount; |
| |
| /** The current number of retries */ |
| private int mRetryCount; |
| |
| /** Random number generator */ |
| private Random mRng = new Random(); |
| |
| private String mConfig; |
| |
| /** Constructor */ |
| public RetryManager() { |
| if (VDBG) log("constructor"); |
| } |
| |
| @Override |
| public String toString() { |
| String ret = "RetryManager: { forever=" + mRetryForever + " maxRetry=" + mMaxRetryCount |
| + " curMaxRetry=" + mCurMaxRetryCount + " retry=" + mRetryCount |
| + " config={" + mConfig + "} retryArray={"; |
| for (RetryRec r : mRetryArray) { |
| ret += r.mDelayTime + ":" + r.mRandomizationTime + " "; |
| } |
| ret += "}}"; |
| return ret; |
| } |
| |
| /** |
| * Configure for a simple linear sequence of times plus |
| * a random value. |
| * |
| * @param maxRetryCount is the maximum number of retries |
| * before isRetryNeeded returns false. |
| * @param retryTime is a time that will be returned by getRetryTime. |
| * @param randomizationTime a random value between 0 and |
| * randomizationTime will be added to retryTime. this |
| * parameter may be 0. |
| * @return true if successful |
| */ |
| public boolean configure(int maxRetryCount, int retryTime, int randomizationTime) { |
| Pair<Boolean, Integer> value; |
| |
| if (VDBG) log("configure: " + maxRetryCount + ", " + retryTime + "," + randomizationTime); |
| |
| if (!validateNonNegativeInt("maxRetryCount", maxRetryCount)) { |
| return false; |
| } |
| |
| if (!validateNonNegativeInt("retryTime", retryTime)) { |
| return false; |
| } |
| |
| if (!validateNonNegativeInt("randomizationTime", randomizationTime)) { |
| return false; |
| } |
| |
| mMaxRetryCount = maxRetryCount; |
| mCurMaxRetryCount = mMaxRetryCount; |
| |
| resetRetryCount(); |
| mRetryArray.clear(); |
| mRetryArray.add(new RetryRec(retryTime, randomizationTime)); |
| |
| return true; |
| } |
| |
| /** |
| * Configure for using string which allow arbitrary |
| * sequences of times. See class comments for the |
| * string format. |
| * |
| * @return true if successful |
| */ |
| public boolean configure(String configStr) { |
| // Strip quotes if present. |
| if ((configStr.startsWith("\"") && configStr.endsWith("\""))) { |
| configStr = configStr.substring(1, configStr.length()-1); |
| } |
| if (VDBG) log("configure: '" + configStr + "'"); |
| mConfig = configStr; |
| |
| if (!TextUtils.isEmpty(configStr)) { |
| int defaultRandomization = 0; |
| |
| if (VDBG) log("configure: not empty"); |
| |
| mMaxRetryCount = 0; |
| resetRetryCount(); |
| mRetryArray.clear(); |
| |
| String strArray[] = configStr.split(","); |
| for (int i = 0; i < strArray.length; i++) { |
| if (VDBG) log("configure: strArray[" + i + "]='" + strArray[i] + "'"); |
| Pair<Boolean, Integer> value; |
| String splitStr[] = strArray[i].split("=", 2); |
| splitStr[0] = splitStr[0].trim(); |
| if (VDBG) log("configure: splitStr[0]='" + splitStr[0] + "'"); |
| if (splitStr.length > 1) { |
| splitStr[1] = splitStr[1].trim(); |
| if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'"); |
| if (TextUtils.equals(splitStr[0], "default_randomization")) { |
| value = parseNonNegativeInt(splitStr[0], splitStr[1]); |
| if (!value.first) return false; |
| defaultRandomization = value.second; |
| } else if (TextUtils.equals(splitStr[0], "max_retries")) { |
| if (TextUtils.equals("infinite",splitStr[1])) { |
| mRetryForever = true; |
| } else { |
| value = parseNonNegativeInt(splitStr[0], splitStr[1]); |
| if (!value.first) return false; |
| mMaxRetryCount = value.second; |
| } |
| } else { |
| Rlog.e(LOG_TAG, "Unrecognized configuration name value pair: " |
| + strArray[i]); |
| return false; |
| } |
| } else { |
| /** |
| * Assume a retry time with an optional randomization value |
| * following a ":" |
| */ |
| splitStr = strArray[i].split(":", 2); |
| splitStr[0] = splitStr[0].trim(); |
| RetryRec rr = new RetryRec(0, 0); |
| value = parseNonNegativeInt("delayTime", splitStr[0]); |
| if (!value.first) return false; |
| rr.mDelayTime = value.second; |
| |
| // Check if optional randomization value present |
| if (splitStr.length > 1) { |
| splitStr[1] = splitStr[1].trim(); |
| if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'"); |
| value = parseNonNegativeInt("randomizationTime", splitStr[1]); |
| if (!value.first) return false; |
| rr.mRandomizationTime = value.second; |
| } else { |
| rr.mRandomizationTime = defaultRandomization; |
| } |
| mRetryArray.add(rr); |
| } |
| } |
| if (mRetryArray.size() > mMaxRetryCount) { |
| mMaxRetryCount = mRetryArray.size(); |
| if (VDBG) log("configure: setting mMaxRetryCount=" + mMaxRetryCount); |
| } |
| mCurMaxRetryCount = mMaxRetryCount; |
| if (VDBG) log("configure: true"); |
| return true; |
| } else { |
| if (VDBG) log("configure: false it's empty"); |
| return false; |
| } |
| } |
| |
| /** |
| * Report whether data reconnection should be retried |
| * |
| * @return {@code true} if the max retries has not been reached. {@code |
| * false} otherwise. |
| */ |
| public boolean isRetryNeeded() { |
| boolean retVal = mRetryForever || (mRetryCount < mCurMaxRetryCount); |
| if (DBG) log("isRetryNeeded: " + retVal); |
| return retVal; |
| } |
| |
| /** |
| * Return the timer that should be used to trigger the data reconnection |
| */ |
| public int getRetryTimer() { |
| int index; |
| if (mRetryCount < mRetryArray.size()) { |
| index = mRetryCount; |
| } else { |
| index = mRetryArray.size() - 1; |
| } |
| |
| int retVal; |
| if ((index >= 0) && (index < mRetryArray.size())) { |
| retVal = mRetryArray.get(index).mDelayTime + nextRandomizationTime(index); |
| } else { |
| retVal = 0; |
| } |
| |
| if (DBG) log("getRetryTimer: " + retVal); |
| return retVal; |
| } |
| |
| /** |
| * @return retry count |
| */ |
| public int getRetryCount() { |
| if (DBG) log("getRetryCount: " + mRetryCount); |
| return mRetryCount; |
| } |
| |
| /** |
| * Increase the retry counter, does not change retry forever. |
| */ |
| public void increaseRetryCount() { |
| mRetryCount++; |
| if (mRetryCount > mCurMaxRetryCount) { |
| mRetryCount = mCurMaxRetryCount; |
| } |
| if (DBG) log("increaseRetryCount: " + mRetryCount); |
| } |
| |
| /** |
| * Set retry count to the specified value |
| */ |
| public void setRetryCount(int count) { |
| mRetryCount = count; |
| if (mRetryCount > mCurMaxRetryCount) { |
| mRetryCount = mCurMaxRetryCount; |
| } |
| |
| if (mRetryCount < 0) { |
| mRetryCount = 0; |
| } |
| |
| if (DBG) log("setRetryCount: " + mRetryCount); |
| } |
| |
| /** |
| * Set current maximum retry count to the specified value |
| */ |
| public void setCurMaxRetryCount(int count) { |
| mCurMaxRetryCount = count; |
| |
| // Make sure it's not negative |
| if (mCurMaxRetryCount < 0) { |
| mCurMaxRetryCount = 0; |
| } |
| |
| // Make sure mRetryCount is within range |
| setRetryCount(mRetryCount); |
| |
| if (DBG) log("setCurMaxRetryCount: " + mCurMaxRetryCount); |
| } |
| |
| /** |
| * Restore CurMaxRetryCount |
| */ |
| public void restoreCurMaxRetryCount() { |
| mCurMaxRetryCount = mMaxRetryCount; |
| |
| // Make sure mRetryCount is within range |
| setRetryCount(mRetryCount); |
| } |
| |
| /** |
| * Set retry forever to the specified value |
| */ |
| public void setRetryForever(boolean retryForever) { |
| mRetryForever = retryForever; |
| if (DBG) log("setRetryForever: " + mRetryForever); |
| } |
| |
| /** |
| * Clear the data-retry counter |
| */ |
| public void resetRetryCount() { |
| mRetryCount = 0; |
| if (DBG) log("resetRetryCount: " + mRetryCount); |
| } |
| |
| /** |
| * Retry forever using last timeout time. |
| */ |
| public void retryForeverUsingLastTimeout() { |
| mRetryCount = mCurMaxRetryCount; |
| mRetryForever = true; |
| if (DBG) log("retryForeverUsingLastTimeout: " + mRetryForever + ", " + mRetryCount); |
| } |
| |
| /** |
| * @return true if retrying forever |
| */ |
| public boolean isRetryForever() { |
| if (DBG) log("isRetryForever: " + mRetryForever); |
| return mRetryForever; |
| } |
| |
| /** |
| * Parse an integer validating the value is not negative. |
| * |
| * @param name |
| * @param stringValue |
| * @return Pair.first == true if stringValue an integer >= 0 |
| */ |
| private Pair<Boolean, Integer> parseNonNegativeInt(String name, String stringValue) { |
| int value; |
| Pair<Boolean, Integer> retVal; |
| try { |
| value = Integer.parseInt(stringValue); |
| retVal = new Pair<Boolean, Integer>(validateNonNegativeInt(name, value), value); |
| } catch (NumberFormatException e) { |
| Rlog.e(LOG_TAG, name + " bad value: " + stringValue, e); |
| retVal = new Pair<Boolean, Integer>(false, 0); |
| } |
| if (VDBG) log("parseNonNetativeInt: " + name + ", " + stringValue + ", " |
| + retVal.first + ", " + retVal.second); |
| return retVal; |
| } |
| |
| /** |
| * Validate an integer is >= 0 and logs an error if not |
| * |
| * @param name |
| * @param value |
| * @return Pair.first |
| */ |
| private boolean validateNonNegativeInt(String name, int value) { |
| boolean retVal; |
| if (value < 0) { |
| Rlog.e(LOG_TAG, name + " bad value: is < 0"); |
| retVal = false; |
| } else { |
| retVal = true; |
| } |
| if (VDBG) log("validateNonNegative: " + name + ", " + value + ", " + retVal); |
| return retVal; |
| } |
| |
| /** |
| * Return next random number for the index |
| */ |
| private int nextRandomizationTime(int index) { |
| int randomTime = mRetryArray.get(index).mRandomizationTime; |
| if (randomTime == 0) { |
| return 0; |
| } else { |
| return mRng.nextInt(randomTime); |
| } |
| } |
| |
| private void log(String s) { |
| Rlog.d(LOG_TAG, "[RM] " + s); |
| } |
| } |