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