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