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 { 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