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.annotation.Nullable; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.content.Context; 22 import android.os.PersistableBundle; 23 import android.os.SystemProperties; 24 import android.telephony.CarrierConfigManager; 25 import android.telephony.data.ApnSetting; 26 import android.text.TextUtils; 27 import android.util.Pair; 28 29 import com.android.internal.telephony.util.TelephonyUtils; 30 import com.android.telephony.Rlog; 31 32 import java.util.ArrayList; 33 import java.util.Random; 34 35 /** 36 * Retry manager allows a simple way to declare a series of 37 * retry timeouts. After creating a RetryManager the configure 38 * method is used to define the sequence. A simple linear series 39 * may be initialized using configure with three integer parameters 40 * The other configure method allows a series to be declared using 41 * a string. 42 *<p> 43 * The format of the configuration string is the apn type followed by a series of parameters 44 * separated by a comma. There are two name value pair parameters plus a series 45 * of delay times. The units of of these delay times is unspecified. 46 * The name value pairs which may be specified are: 47 *<ul> 48 *<li>max_retries=<value> 49 *<li>default_randomizationTime=<value> 50 *</ul> 51 *<p> 52 * apn type specifies the APN type that the retry pattern will apply for. "others" is for all other 53 * APN types not specified in the config. 54 * 55 * max_retries is the number of times that incrementRetryCount 56 * maybe called before isRetryNeeded will return false. if value 57 * is infinite then isRetryNeeded will always return true. 58 * 59 * default_randomizationTime will be used as the randomizationTime 60 * for delay times which have no supplied randomizationTime. If 61 * default_randomizationTime is not defined it defaults to 0. 62 *<p> 63 * The other parameters define The series of delay times and each 64 * may have an optional randomization value separated from the 65 * delay time by a colon. 66 *<p> 67 * Examples: 68 * <ul> 69 * <li>3 retries for mms with no randomization value which means its 0: 70 * <ul><li><code>"mms:1000, 2000, 3000"</code></ul> 71 * 72 * <li>10 retries for default APN with a 500 default randomization value for each and 73 * the 4..10 retries all using 3000 as the delay: 74 * <ul><li><code>"default:max_retries=10, default_randomization=500, 1000, 2000, 3000"</code></ul> 75 * 76 * <li>4 retries for supl APN with a 100 as the default randomization value for the first 2 values 77 * and the other two having specified values of 500: 78 * <ul><li><code>"supl:default_randomization=100, 1000, 2000, 4000:500, 5000:500"</code></ul> 79 * 80 * <li>Infinite number of retries for all other APNs with the first one at 1000, the second at 2000 81 * all others will be at 3000. 82 * <ul><li><code>"others:max_retries=infinite,1000,2000,3000</code></ul> 83 * </ul> 84 * 85 * {@hide} 86 */ 87 public class RetryManager { 88 public static final String LOG_TAG = "RetryManager"; 89 public static final boolean DBG = true; 90 public static final boolean VDBG = false; // STOPSHIP if true 91 92 /** 93 * The default retry configuration for APNs. See above for the syntax. 94 */ 95 private static final String DEFAULT_DATA_RETRY_CONFIG = "max_retries=3, 5000, 5000, 5000"; 96 97 /** 98 * The APN type used for all other APNs retry configuration. 99 */ 100 private static final String OTHERS_APN_TYPE = "others"; 101 102 /** 103 * The default value (in milliseconds) for delay between APN trying (mInterApnDelay) 104 * within the same round 105 */ 106 private static final long DEFAULT_INTER_APN_DELAY = 20000; 107 108 /** 109 * The default value (in milliseconds) for delay between APN trying (mFailFastInterApnDelay) 110 * within the same round when we are in fail fast mode 111 */ 112 private static final long DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING = 3000; 113 114 /** 115 * The default value (in milliseconds) for retrying APN after disconnect 116 */ 117 private static final long DEFAULT_APN_RETRY_AFTER_DISCONNECT_DELAY = 10000; 118 119 /** 120 * The value indicating no retry is needed 121 */ 122 public static final long NO_RETRY = -1; 123 124 /** 125 * The value indicating modem did not suggest any retry delay 126 */ 127 public static final long NO_SUGGESTED_RETRY_DELAY = -2; 128 129 /** 130 * If the modem suggests a retry delay in the data call setup response, we will retry 131 * the current APN setting again. However, if the modem keeps suggesting retrying the same 132 * APN setting, we'll fall into an infinite loop. Therefore adding a counter to retry up to 133 * MAX_SAME_APN_RETRY times can avoid it. 134 */ 135 private static final int MAX_SAME_APN_RETRY = 3; 136 137 /** 138 * The delay (in milliseconds) between APN trying within the same round 139 */ 140 @UnsupportedAppUsage 141 private long mInterApnDelay; 142 143 /** 144 * The delay (in milliseconds) between APN trying within the same round when we are in 145 * fail fast mode 146 */ 147 @UnsupportedAppUsage 148 private long mFailFastInterApnDelay; 149 150 /** 151 * The delay (in milliseconds) for APN retrying after disconnect (e.g. Modem suddenly reports 152 * data call lost) 153 */ 154 private long mApnRetryAfterDisconnectDelay; 155 156 /** 157 * Modem suggested delay for retrying the current APN 158 */ 159 private long mModemSuggestedDelay = NO_SUGGESTED_RETRY_DELAY; 160 161 /** 162 * The counter for same APN retrying. See MAX_SAME_APN_RETRY for the details. 163 */ 164 private int mSameApnRetryCount = 0; 165 166 /** 167 * Retry record with times in milli-seconds 168 */ 169 private static class RetryRec { RetryRec(int delayTime, int randomizationTime)170 RetryRec(int delayTime, int randomizationTime) { 171 mDelayTime = delayTime; 172 mRandomizationTime = randomizationTime; 173 } 174 175 int mDelayTime; 176 int mRandomizationTime; 177 } 178 179 /** 180 * The array of retry records 181 */ 182 private ArrayList<RetryRec> mRetryArray = new ArrayList<RetryRec>(); 183 184 @UnsupportedAppUsage 185 private Phone mPhone; 186 187 /** 188 * Flag indicating whether retrying forever regardless the maximum retry count mMaxRetryCount 189 */ 190 private boolean mRetryForever = false; 191 192 /** 193 * The maximum number of retries to attempt 194 */ 195 private int mMaxRetryCount; 196 197 /** 198 * The current number of retries 199 */ 200 private int mRetryCount = 0; 201 202 /** 203 * Random number generator. The random delay will be added into retry timer to avoid all devices 204 * around retrying the APN at the same time. 205 */ 206 private Random mRng = new Random(); 207 208 /** 209 * Retry manager configuration string. See top of the detailed explanation. 210 */ 211 private String mConfig; 212 213 /** 214 * The list to store APN setting candidates for data call setup. Most of the carriers only have 215 * one APN, but few carriers have more than one. 216 */ 217 private ArrayList<ApnSetting> mWaitingApns = null; 218 219 /** 220 * Index pointing to the current trying APN from mWaitingApns 221 */ 222 private int mCurrentApnIndex = -1; 223 224 /** 225 * Apn context type. Could be "default, "mms", "supl", etc... 226 */ 227 @UnsupportedAppUsage 228 private String mApnType; 229 230 /** 231 * Retry manager constructor 232 * @param phone Phone object 233 * @param apnType APN type 234 */ RetryManager(Phone phone, String apnType)235 public RetryManager(Phone phone, String apnType) { 236 mPhone = phone; 237 mApnType = apnType; 238 } 239 240 /** 241 * Configure for using string which allow arbitrary 242 * sequences of times. See class comments for the 243 * string format. 244 * 245 * @return true if successful 246 */ 247 @UnsupportedAppUsage configure(String configStr)248 private boolean configure(String configStr) { 249 // Strip quotes if present. 250 if ((configStr.startsWith("\"") && configStr.endsWith("\""))) { 251 configStr = configStr.substring(1, configStr.length() - 1); 252 } 253 254 // Reset the retry manager since delay, max retry count, etc...will be reset. 255 reset(); 256 257 if (DBG) log("configure: '" + configStr + "'"); 258 mConfig = configStr; 259 260 if (!TextUtils.isEmpty(configStr)) { 261 int defaultRandomization = 0; 262 263 if (VDBG) log("configure: not empty"); 264 265 String strArray[] = configStr.split(","); 266 for (int i = 0; i < strArray.length; i++) { 267 if (VDBG) log("configure: strArray[" + i + "]='" + strArray[i] + "'"); 268 Pair<Boolean, Integer> value; 269 String splitStr[] = strArray[i].split("=", 2); 270 splitStr[0] = splitStr[0].trim(); 271 if (VDBG) log("configure: splitStr[0]='" + splitStr[0] + "'"); 272 if (splitStr.length > 1) { 273 splitStr[1] = splitStr[1].trim(); 274 if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'"); 275 if (TextUtils.equals(splitStr[0], "default_randomization")) { 276 value = parseNonNegativeInt(splitStr[0], splitStr[1]); 277 if (!value.first) return false; 278 defaultRandomization = value.second; 279 } else if (TextUtils.equals(splitStr[0], "max_retries")) { 280 if (TextUtils.equals("infinite", splitStr[1])) { 281 mRetryForever = true; 282 } else { 283 value = parseNonNegativeInt(splitStr[0], splitStr[1]); 284 if (!value.first) return false; 285 mMaxRetryCount = value.second; 286 } 287 } else { 288 Rlog.e(LOG_TAG, "Unrecognized configuration name value pair: " 289 + strArray[i]); 290 return false; 291 } 292 } else { 293 /** 294 * Assume a retry time with an optional randomization value 295 * following a ":" 296 */ 297 splitStr = strArray[i].split(":", 2); 298 splitStr[0] = splitStr[0].trim(); 299 RetryRec rr = new RetryRec(0, 0); 300 value = parseNonNegativeInt("delayTime", splitStr[0]); 301 if (!value.first) return false; 302 rr.mDelayTime = value.second; 303 304 // Check if optional randomization value present 305 if (splitStr.length > 1) { 306 splitStr[1] = splitStr[1].trim(); 307 if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'"); 308 value = parseNonNegativeInt("randomizationTime", splitStr[1]); 309 if (!value.first) return false; 310 rr.mRandomizationTime = value.second; 311 } else { 312 rr.mRandomizationTime = defaultRandomization; 313 } 314 mRetryArray.add(rr); 315 } 316 } 317 if (mRetryArray.size() > mMaxRetryCount) { 318 mMaxRetryCount = mRetryArray.size(); 319 if (VDBG) log("configure: setting mMaxRetryCount=" + mMaxRetryCount); 320 } 321 } else { 322 log("configure: cleared"); 323 } 324 325 if (VDBG) log("configure: true"); 326 return true; 327 } 328 329 /** 330 * Configure the retry manager 331 */ configureRetry()332 private void configureRetry() { 333 String configString = null; 334 String otherConfigString = null; 335 336 try { 337 if (TelephonyUtils.IS_DEBUGGABLE) { 338 // Using system properties is easier for testing from command line. 339 String config = SystemProperties.get("test.data_retry_config"); 340 if (!TextUtils.isEmpty(config)) { 341 configure(config); 342 return; 343 } 344 } 345 346 CarrierConfigManager configManager = (CarrierConfigManager) 347 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 348 PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId()); 349 350 mInterApnDelay = b.getLong( 351 CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 352 DEFAULT_INTER_APN_DELAY); 353 mFailFastInterApnDelay = b.getLong( 354 CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 355 DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING); 356 mApnRetryAfterDisconnectDelay = b.getLong( 357 CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 358 DEFAULT_APN_RETRY_AFTER_DISCONNECT_DELAY); 359 360 // Load all retry patterns for all different APNs. 361 String[] allConfigStrings = b.getStringArray( 362 CarrierConfigManager.KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS); 363 if (allConfigStrings != null) { 364 for (String s : allConfigStrings) { 365 if (!TextUtils.isEmpty(s)) { 366 String splitStr[] = s.split(":", 2); 367 if (splitStr.length == 2) { 368 String apnType = splitStr[0].trim(); 369 // Check if this retry pattern is for the APN we want. 370 if (apnType.equals(mApnType)) { 371 // Extract the config string. Note that an empty string is valid 372 // here, meaning no retry for the specified APN. 373 configString = splitStr[1]; 374 break; 375 } else if (apnType.equals(OTHERS_APN_TYPE)) { 376 // Extract the config string. Note that an empty string is valid 377 // here, meaning no retry for all other APNs. 378 otherConfigString = splitStr[1]; 379 } 380 } 381 } 382 } 383 } 384 385 if (configString == null) { 386 if (otherConfigString != null) { 387 configString = otherConfigString; 388 } else { 389 // We should never reach here. If we reach here, it must be a configuration 390 // error bug. 391 log("Invalid APN retry configuration!. Use the default one now."); 392 configString = DEFAULT_DATA_RETRY_CONFIG; 393 } 394 } 395 } catch (NullPointerException ex) { 396 // We should never reach here unless there is a bug 397 log("Failed to read configuration! Use the hardcoded default value."); 398 399 mInterApnDelay = DEFAULT_INTER_APN_DELAY; 400 mFailFastInterApnDelay = DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING; 401 configString = DEFAULT_DATA_RETRY_CONFIG; 402 } 403 404 if (VDBG) { 405 log("mInterApnDelay = " + mInterApnDelay + ", mFailFastInterApnDelay = " + 406 mFailFastInterApnDelay); 407 } 408 409 configure(configString); 410 } 411 412 /** 413 * Return the timer that should be used to trigger the data reconnection 414 */ 415 @UnsupportedAppUsage getRetryTimer()416 private int getRetryTimer() { 417 int index; 418 if (mRetryCount < mRetryArray.size()) { 419 index = mRetryCount; 420 } else { 421 index = mRetryArray.size() - 1; 422 } 423 424 int retVal; 425 if ((index >= 0) && (index < mRetryArray.size())) { 426 retVal = mRetryArray.get(index).mDelayTime + nextRandomizationTime(index); 427 } else { 428 retVal = 0; 429 } 430 431 if (DBG) log("getRetryTimer: " + retVal); 432 return retVal; 433 } 434 435 /** 436 * Parse an integer validating the value is not negative. 437 * @param name Name 438 * @param stringValue Value 439 * @return Pair.first == true if stringValue an integer >= 0 440 */ parseNonNegativeInt(String name, String stringValue)441 private Pair<Boolean, Integer> parseNonNegativeInt(String name, String stringValue) { 442 int value; 443 Pair<Boolean, Integer> retVal; 444 try { 445 value = Integer.parseInt(stringValue); 446 retVal = new Pair<Boolean, Integer>(validateNonNegativeInt(name, value), value); 447 } catch (NumberFormatException e) { 448 Rlog.e(LOG_TAG, name + " bad value: " + stringValue, e); 449 retVal = new Pair<Boolean, Integer>(false, 0); 450 } 451 if (VDBG) { 452 log("parseNonNetativeInt: " + name + ", " + stringValue + ", " 453 + retVal.first + ", " + retVal.second); 454 } 455 return retVal; 456 } 457 458 /** 459 * Validate an integer is >= 0 and logs an error if not 460 * @param name Name 461 * @param value Value 462 * @return Pair.first 463 */ validateNonNegativeInt(String name, int value)464 private boolean validateNonNegativeInt(String name, int value) { 465 boolean retVal; 466 if (value < 0) { 467 Rlog.e(LOG_TAG, name + " bad value: is < 0"); 468 retVal = false; 469 } else { 470 retVal = true; 471 } 472 if (VDBG) log("validateNonNegative: " + name + ", " + value + ", " + retVal); 473 return retVal; 474 } 475 476 /** 477 * Return next random number for the index 478 * @param index Retry index 479 */ nextRandomizationTime(int index)480 private int nextRandomizationTime(int index) { 481 int randomTime = mRetryArray.get(index).mRandomizationTime; 482 if (randomTime == 0) { 483 return 0; 484 } else { 485 return mRng.nextInt(randomTime); 486 } 487 } 488 489 /** 490 * Get the next APN setting for data call setup. 491 * @return APN setting to try. {@code null} if cannot find any APN, 492 */ getNextApnSetting()493 public @Nullable ApnSetting getNextApnSetting() { 494 if (mWaitingApns == null || mWaitingApns.size() == 0) { 495 log("Waiting APN list is null or empty."); 496 return null; 497 } 498 499 // If the modem had suggested a retry delay, we should retry the current APN again 500 // (up to MAX_SAME_APN_RETRY times) instead of getting the next APN setting from 501 // our own list. If the APN waiting list has been reset before a setup data responses 502 // arrive (i.e. mCurrentApnIndex=-1), then ignore the modem suggested retry. 503 if (mCurrentApnIndex != -1 && mModemSuggestedDelay != NO_SUGGESTED_RETRY_DELAY 504 && mSameApnRetryCount < MAX_SAME_APN_RETRY) { 505 mSameApnRetryCount++; 506 return mWaitingApns.get(mCurrentApnIndex); 507 } 508 509 mSameApnRetryCount = 0; 510 511 int index = mCurrentApnIndex; 512 // Loop through the APN list to find out the index of next non-permanent failed APN. 513 while (true) { 514 if (++index == mWaitingApns.size()) index = 0; 515 516 // Stop if we find the non-failed APN. 517 if (!mWaitingApns.get(index).getPermanentFailed()) { 518 break; 519 } 520 521 // If all APNs have permanently failed, bail out. 522 if (mWaitingApns.stream().allMatch(ApnSetting::getPermanentFailed)) { 523 return null; 524 } 525 } 526 527 mCurrentApnIndex = index; 528 return mWaitingApns.get(mCurrentApnIndex); 529 } 530 531 /** 532 * Get the delay for trying the next waiting APN from the list. 533 * @param failFastEnabled True if fail fast mode enabled. In this case we'll use a shorter 534 * delay. 535 * @return delay in milliseconds 536 */ getDelayForNextApn(boolean failFastEnabled)537 public long getDelayForNextApn(boolean failFastEnabled) { 538 539 if (mWaitingApns == null || mWaitingApns.size() == 0) { 540 log("Waiting APN list is null or empty."); 541 return NO_RETRY; 542 } 543 544 if (mModemSuggestedDelay == NO_RETRY) { 545 log("Modem suggested not retrying."); 546 return NO_RETRY; 547 } 548 549 if (mModemSuggestedDelay != NO_SUGGESTED_RETRY_DELAY && 550 mSameApnRetryCount < MAX_SAME_APN_RETRY) { 551 // If the modem explicitly suggests a retry delay, we should use it, even in fail fast 552 // mode. 553 log("Modem suggested retry in " + mModemSuggestedDelay + " ms."); 554 return mModemSuggestedDelay; 555 } 556 557 // In order to determine the delay to try next APN, we need to peek the next available APN. 558 // Case 1 - If we will start the next round of APN trying, 559 // we use the exponential-growth delay. (e.g. 5s, 10s, 30s...etc.) 560 // Case 2 - If we are still within the same round of APN trying, 561 // we use the fixed standard delay between APNs. (e.g. 20s) 562 563 int index = mCurrentApnIndex; 564 while (true) { 565 if (++index >= mWaitingApns.size()) index = 0; 566 567 // Stop if we find the non-failed APN. 568 if (!mWaitingApns.get(index).getPermanentFailed()) { 569 break; 570 } 571 572 // If all APNs have permanently failed, bail out. 573 if (mWaitingApns.stream().allMatch(ApnSetting::getPermanentFailed)) { 574 log("All APNs have permanently failed."); 575 return NO_RETRY; 576 } 577 } 578 579 long delay; 580 if (index <= mCurrentApnIndex) { 581 // Case 1, if the next APN is in the next round. 582 if (!mRetryForever && mRetryCount + 1 > mMaxRetryCount) { 583 log("Reached maximum retry count " + mMaxRetryCount + "."); 584 return NO_RETRY; 585 } 586 delay = getRetryTimer(); 587 ++mRetryCount; 588 } else { 589 // Case 2, if the next APN is still in the same round. 590 delay = mInterApnDelay; 591 } 592 593 if (failFastEnabled && delay > mFailFastInterApnDelay) { 594 // If we enable fail fast mode, and the delay we got is longer than 595 // fail-fast delay (mFailFastInterApnDelay), use the fail-fast delay. 596 // If the delay we calculated is already shorter than fail-fast delay, 597 // then ignore fail-fast delay. 598 delay = mFailFastInterApnDelay; 599 } 600 601 return delay; 602 } 603 604 /** 605 * Mark the APN setting permanently failed. 606 * @param apn APN setting to be marked as permanently failed 607 * */ markApnPermanentFailed(ApnSetting apn)608 public void markApnPermanentFailed(ApnSetting apn) { 609 if (apn != null) { 610 apn.setPermanentFailed(true); 611 } 612 } 613 614 /** 615 * Reset the retry manager. 616 */ reset()617 private void reset() { 618 mMaxRetryCount = 0; 619 mRetryCount = 0; 620 mCurrentApnIndex = -1; 621 mSameApnRetryCount = 0; 622 mModemSuggestedDelay = NO_SUGGESTED_RETRY_DELAY; 623 mRetryArray.clear(); 624 } 625 626 /** 627 * Set waiting APNs for retrying in case needed. 628 * @param waitingApns Waiting APN list 629 */ setWaitingApns(ArrayList<ApnSetting> waitingApns)630 public void setWaitingApns(ArrayList<ApnSetting> waitingApns) { 631 632 if (waitingApns == null) { 633 log("No waiting APNs provided"); 634 return; 635 } 636 637 mWaitingApns = waitingApns; 638 639 // Since we replace the entire waiting APN list, we need to re-config this retry manager. 640 configureRetry(); 641 642 for (ApnSetting apn : mWaitingApns) { 643 apn.setPermanentFailed(false); 644 } 645 646 log("Setting " + mWaitingApns.size() + " waiting APNs."); 647 648 if (VDBG) { 649 for (int i = 0; i < mWaitingApns.size(); i++) { 650 log(" [" + i + "]:" + mWaitingApns.get(i)); 651 } 652 } 653 } 654 655 /** 656 * Get the list of waiting APNs. 657 * @return the list of waiting APNs 658 */ getWaitingApns()659 public ArrayList<ApnSetting> getWaitingApns() { 660 return mWaitingApns; 661 } 662 663 /** 664 * Save the modem suggested delay for retrying the current APN. 665 * This method is called when we get the suggested delay from RIL. 666 * @param delay The delay in milliseconds 667 */ setModemSuggestedDelay(long delay)668 public void setModemSuggestedDelay(long delay) { 669 if (mCurrentApnIndex == -1) { 670 log("Waiting APN list has been reset. Ignore the value from modem."); 671 return; 672 } 673 mModemSuggestedDelay = delay; 674 } 675 676 /** 677 * Get the delay in milliseconds for APN retry after disconnect 678 * @return The delay in milliseconds 679 */ getRetryAfterDisconnectDelay()680 public long getRetryAfterDisconnectDelay() { 681 return mApnRetryAfterDisconnectDelay; 682 } 683 toString()684 public String toString() { 685 if (mConfig == null) return ""; 686 return "RetryManager: mApnType=" + mApnType + " mRetryCount=" + mRetryCount 687 + " mMaxRetryCount=" + mMaxRetryCount + " mCurrentApnIndex=" + mCurrentApnIndex 688 + " mSameApnRtryCount=" + mSameApnRetryCount + " mModemSuggestedDelay=" 689 + mModemSuggestedDelay + " mRetryForever=" + mRetryForever + " mInterApnDelay=" 690 + mInterApnDelay + " mApnRetryAfterDisconnectDelay=" + mApnRetryAfterDisconnectDelay 691 + " mConfig={" + mConfig + "}"; 692 } 693 694 @UnsupportedAppUsage log(String s)695 private void log(String s) { 696 Rlog.d(LOG_TAG, "[" + mApnType + "] " + s); 697 } 698 } 699