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