1 /* 2 * Copyright (C) 2006 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.dataconnection; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.net.NetworkCapabilities; 22 import android.net.NetworkRequest; 23 import android.os.Message; 24 import android.telephony.Annotation.ApnType; 25 import android.telephony.data.ApnSetting; 26 import android.text.TextUtils; 27 import android.util.ArraySet; 28 import android.util.LocalLog; 29 import android.util.SparseIntArray; 30 31 import com.android.internal.R; 32 import com.android.internal.telephony.DctConstants; 33 import com.android.internal.telephony.Phone; 34 import com.android.internal.telephony.RetryManager; 35 import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType; 36 import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType; 37 import com.android.internal.util.IndentingPrintWriter; 38 import com.android.telephony.Rlog; 39 40 import java.io.FileDescriptor; 41 import java.io.PrintWriter; 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.concurrent.atomic.AtomicBoolean; 45 import java.util.concurrent.atomic.AtomicInteger; 46 47 /** 48 * Maintain the Apn context 49 */ 50 public class ApnContext { 51 52 public final String LOG_TAG; 53 private final static String SLOG_TAG = "ApnContext"; 54 55 protected static final boolean DBG = false; 56 57 private final Phone mPhone; 58 59 private final String mApnType; 60 61 private DctConstants.State mState; 62 63 private int mPriority; 64 65 private ApnSetting mApnSetting; 66 67 private DataConnection mDataConnection; 68 69 private String mReason; 70 71 /** 72 * user/app requested connection on this APN 73 */ 74 AtomicBoolean mDataEnabled; 75 76 private final Object mRefCountLock = new Object(); 77 78 private final DcTracker mDcTracker; 79 80 81 /** 82 * Remember this as a change in this value to a more permissive state 83 * should cause us to retry even permanent failures 84 */ 85 private boolean mConcurrentVoiceAndDataAllowed; 86 87 /** 88 * used to track a single connection request so disconnects can get ignored if 89 * obsolete. 90 */ 91 private final AtomicInteger mConnectionGeneration = new AtomicInteger(0); 92 93 /** 94 * Retry manager that handles the APN retry and delays. 95 */ 96 private final RetryManager mRetryManager; 97 98 /** 99 * ApnContext constructor 100 * @param phone phone object 101 * @param typeId APN type Id 102 * @param logTag Tag for logging 103 * @param tracker Data call tracker 104 * @param priority Priority of APN type 105 */ ApnContext(Phone phone, int typeId, String logTag, DcTracker tracker, int priority)106 public ApnContext(Phone phone, int typeId, String logTag, DcTracker tracker, int priority) { 107 this(phone, ApnSetting.getApnTypeString(typeId), logTag, tracker, priority); 108 } 109 110 /** 111 * ApnContext constructor 112 * @param phone phone object 113 * @param apnType APN type (e.g. default, supl, mms, etc...) 114 * @param logTag Tag for logging 115 * @param tracker Data call tracker 116 * @param priority Priority of APN type 117 */ ApnContext(Phone phone, String apnType, String logTag, DcTracker tracker, int priority)118 public ApnContext(Phone phone, String apnType, String logTag, DcTracker tracker, int priority) { 119 mPhone = phone; 120 mApnType = apnType; 121 mState = DctConstants.State.IDLE; 122 setReason(Phone.REASON_DATA_ENABLED); 123 mDataEnabled = new AtomicBoolean(false); 124 mPriority = priority; 125 LOG_TAG = logTag; 126 mDcTracker = tracker; 127 mRetryManager = new RetryManager(phone, tracker.getDataThrottler(), 128 ApnSetting.getApnTypesBitmaskFromString(apnType)); 129 } 130 131 132 133 /** 134 * Get the APN type 135 * @return The APN type 136 */ getApnType()137 public String getApnType() { 138 return mApnType; 139 } 140 141 /** 142 * Gets the APN type bitmask. 143 * @return The APN type bitmask 144 */ getApnTypeBitmask()145 public int getApnTypeBitmask() { 146 return ApnSetting.getApnTypesBitmaskFromString(mApnType); 147 } 148 149 /** 150 * Get the associated data connection 151 * @return The data connection 152 */ getDataConnection()153 public synchronized DataConnection getDataConnection() { 154 return mDataConnection; 155 } 156 157 /** 158 * This priority is taken into account when concurrent data connections are not allowed. The 159 * APN with the HIGHER priority is given preference. 160 * @return The priority of the APN type 161 */ getPriority()162 public int getPriority() { 163 return mPriority; 164 } 165 166 /** 167 * Updates the priority of this context. 168 * @param priority The priority of the APN type 169 */ setPriority(int priority)170 public void setPriority(int priority) { 171 mPriority = priority; 172 } 173 174 /** 175 * Keeping for backwards compatibility and in case it's needed in the future 176 * @return true 177 */ isDependencyMet()178 public boolean isDependencyMet() { 179 return true; 180 } 181 182 /** 183 * Set the associated data connection. 184 * @param dc data connection 185 */ setDataConnection(DataConnection dc)186 public synchronized void setDataConnection(DataConnection dc) { 187 log("setDataConnectionAc: old=" + mDataConnection + ",new=" + dc + " this=" + this); 188 mDataConnection = dc; 189 } 190 191 /** 192 * Release data connection. 193 * @param reason The reason of releasing data connection 194 */ releaseDataConnection(String reason)195 public synchronized void releaseDataConnection(String reason) { 196 if (mDataConnection != null) { 197 mDataConnection.tearDown(this, reason, null); 198 mDataConnection = null; 199 } 200 setState(DctConstants.State.IDLE); 201 } 202 203 /** 204 * Get the current APN setting. 205 * @return APN setting 206 */ getApnSetting()207 public synchronized ApnSetting getApnSetting() { 208 log("getApnSetting: apnSetting=" + mApnSetting); 209 return mApnSetting; 210 } 211 212 /** 213 * Set the APN setting. 214 * @param apnSetting APN setting 215 */ setApnSetting(ApnSetting apnSetting)216 public synchronized void setApnSetting(ApnSetting apnSetting) { 217 log("setApnSetting: apnSetting=" + apnSetting); 218 mApnSetting = apnSetting; 219 } 220 221 /** 222 * Set the list of APN candidates which will be used for data call setup later. 223 * @param waitingApns List of APN candidates 224 */ setWaitingApns(ArrayList<ApnSetting> waitingApns)225 public synchronized void setWaitingApns(ArrayList<ApnSetting> waitingApns) { 226 mRetryManager.setWaitingApns(waitingApns); 227 } 228 229 /** 230 * Get the next available APN to try. 231 * @return APN setting which will be used for data call setup.{@code null} if there is no 232 * APN can be retried. 233 */ getNextApnSetting()234 public @Nullable ApnSetting getNextApnSetting() { 235 return mRetryManager.getNextApnSetting(); 236 } 237 238 /** 239 * Get the delay for trying the next APN setting if the current one failed. 240 * @param failFastEnabled True if fail fast mode enabled. In this case we'll use a shorter 241 * delay. 242 * @return The delay in milliseconds 243 */ getDelayForNextApn(boolean failFastEnabled)244 public long getDelayForNextApn(boolean failFastEnabled) { 245 return mRetryManager.getDelayForNextApn(failFastEnabled || isFastRetryReason()); 246 } 247 248 /** 249 * Mark the current APN setting permanently failed, which means it will not be retried anymore. 250 * @param apn APN setting 251 */ markApnPermanentFailed(ApnSetting apn)252 public void markApnPermanentFailed(ApnSetting apn) { 253 mRetryManager.markApnPermanentFailed(apn); 254 } 255 256 /** 257 * Get the list of waiting APNs. 258 * @return the list of waiting APNs 259 */ getWaitingApns()260 public @NonNull ArrayList<ApnSetting> getWaitingApns() { 261 return mRetryManager.getWaitingApns(); 262 } 263 264 /** 265 * Save the state indicating concurrent voice/data allowed. 266 * @param allowed True if concurrent voice/data is allowed 267 */ setConcurrentVoiceAndDataAllowed(boolean allowed)268 public synchronized void setConcurrentVoiceAndDataAllowed(boolean allowed) { 269 mConcurrentVoiceAndDataAllowed = allowed; 270 } 271 272 /** 273 * Get the state indicating concurrent voice/data allowed. 274 * @return True if concurrent voice/data is allowed 275 */ isConcurrentVoiceAndDataAllowed()276 public synchronized boolean isConcurrentVoiceAndDataAllowed() { 277 return mConcurrentVoiceAndDataAllowed; 278 } 279 280 /** 281 * Set the current data call state. 282 * @param s Current data call state 283 */ setState(DctConstants.State s)284 public synchronized void setState(DctConstants.State s) { 285 log("setState: " + s + ", previous state:" + mState); 286 287 if (mState != s) { 288 mStateLocalLog.log("State changed from " + mState + " to " + s); 289 mState = s; 290 } 291 292 if (mState == DctConstants.State.FAILED) { 293 // when teardown the connection and set to IDLE 294 mRetryManager.getWaitingApns().clear(); 295 } 296 } 297 298 /** 299 * Get the current data call state. 300 * @return The current data call state 301 */ getState()302 public synchronized DctConstants.State getState() { 303 return mState; 304 } 305 306 /** 307 * Check whether the data call is disconnected or not. 308 * @return True if the data call is disconnected 309 */ isDisconnected()310 public boolean isDisconnected() { 311 DctConstants.State currentState = getState(); 312 return ((currentState == DctConstants.State.IDLE) || 313 currentState == DctConstants.State.FAILED); 314 } 315 316 /** 317 * Set the reason for data call connection. 318 * @param reason Reason for data call connection 319 */ setReason(String reason)320 public synchronized void setReason(String reason) { 321 log("set reason as " + reason + ",current state " + mState); 322 mReason = reason; 323 } 324 325 /** 326 * Get the reason for data call connection. 327 * @return The reason for data call connection 328 */ getReason()329 public synchronized String getReason() { 330 return mReason; 331 } 332 333 /** 334 * Check if ready for data call connection 335 * @return True if ready, otherwise false. 336 */ isReady()337 public boolean isReady() { 338 return mDataEnabled.get() && isDependencyMet(); 339 } 340 341 /** 342 * Check if the data call is in the state which allow connecting. 343 * @return True if allowed, otherwise false. 344 */ isConnectable()345 public boolean isConnectable() { 346 return isReady() && ((mState == DctConstants.State.IDLE) 347 || (mState == DctConstants.State.RETRYING) 348 || (mState == DctConstants.State.FAILED)); 349 } 350 351 /** 352 * Check if apn reason is fast retry reason which should apply shorter delay between apn re-try. 353 * @return True if it is fast retry reason, otherwise false. 354 */ isFastRetryReason()355 private boolean isFastRetryReason() { 356 return Phone.REASON_NW_TYPE_CHANGED.equals(mReason) || 357 Phone.REASON_APN_CHANGED.equals(mReason); 358 } 359 360 /** Check if the data call is in connected or connecting state. 361 * @return True if the data call is in connected or connecting state 362 */ isConnectedOrConnecting()363 public boolean isConnectedOrConnecting() { 364 return isReady() && ((mState == DctConstants.State.CONNECTED) 365 || (mState == DctConstants.State.CONNECTING) 366 || (mState == DctConstants.State.RETRYING)); 367 } 368 369 /** 370 * Set data call enabled/disabled state. 371 * @param enabled True if data call is enabled 372 */ setEnabled(boolean enabled)373 public void setEnabled(boolean enabled) { 374 log("set enabled as " + enabled + ", current state is " + mDataEnabled.get()); 375 mDataEnabled.set(enabled); 376 } 377 378 /** 379 * Check if the data call is enabled or not. 380 * @return True if enabled 381 */ isEnabled()382 public boolean isEnabled() { 383 return mDataEnabled.get(); 384 } 385 isProvisioningApn()386 public boolean isProvisioningApn() { 387 String provisioningApn = mPhone.getContext().getResources() 388 .getString(R.string.mobile_provisioning_apn); 389 if (!TextUtils.isEmpty(provisioningApn) && 390 (mApnSetting != null) && (mApnSetting.getApnName() != null)) { 391 return (mApnSetting.getApnName().equals(provisioningApn)); 392 } else { 393 return false; 394 } 395 } 396 397 private final ArraySet<NetworkRequest> mNetworkRequests = new ArraySet<>(); 398 private final LocalLog mStateLocalLog = new LocalLog(32); 399 400 private static final LocalLog sLocalLog = new LocalLog(256); 401 402 /** Add a line to the ApnContext local log. */ requestLog(ApnContext apnContext, String str)403 public static void requestLog(ApnContext apnContext, String str) { 404 if (apnContext != null) { 405 String logString = "[ApnContext:" + apnContext.getApnType() + "] " + str; 406 if (DBG) { 407 Rlog.d(SLOG_TAG, logString); 408 } 409 synchronized (sLocalLog) { 410 sLocalLog.log(logString); 411 } 412 } 413 } 414 415 /** 416 * Request a network 417 * 418 * @param networkRequest Network request from clients 419 * @param type The request type 420 * @param onHandoverCompleteMsg When request type is handover, this message will be sent when 421 * handover is completed. For normal request, this should be null. 422 */ requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type, Message onHandoverCompleteMsg)423 public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type, 424 Message onHandoverCompleteMsg) { 425 synchronized (mRefCountLock) { 426 mNetworkRequests.add(networkRequest); 427 requestLog(this, "requestNetwork for " + networkRequest + ", type=" 428 + DcTracker.requestTypeToString(type)); 429 mDcTracker.enableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type, 430 onHandoverCompleteMsg); 431 if (mDataConnection != null) { 432 // New network request added. Should re-evaluate properties of 433 // the data connection. For example, the score may change. 434 mDataConnection.reevaluateDataConnectionProperties(); 435 } 436 } 437 } 438 releaseNetwork(NetworkRequest networkRequest, @ReleaseNetworkType int type)439 public void releaseNetwork(NetworkRequest networkRequest, @ReleaseNetworkType int type) { 440 synchronized (mRefCountLock) { 441 if (mNetworkRequests.contains(networkRequest)) { 442 mNetworkRequests.remove(networkRequest); 443 if (mDataConnection != null) { 444 // New network request added. Should re-evaluate properties of 445 // the data connection. For example, the score may change. 446 mDataConnection.reevaluateDataConnectionProperties(); 447 } 448 requestLog(this, "releaseNetwork left with " + mNetworkRequests.size() 449 + " requests."); 450 if (mNetworkRequests.size() == 0 451 || type == DcTracker.RELEASE_TYPE_DETACH 452 || type == DcTracker.RELEASE_TYPE_HANDOVER) { 453 mDcTracker.disableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type); 454 } 455 } 456 } 457 } 458 459 /** 460 * @param excludeDun True if excluding requests that have DUN capability 461 * @return True if the attached network requests contain restricted capability. 462 */ hasRestrictedRequests(boolean excludeDun)463 public boolean hasRestrictedRequests(boolean excludeDun) { 464 synchronized (mRefCountLock) { 465 for (NetworkRequest nr : mNetworkRequests) { 466 if (excludeDun && 467 nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) { 468 continue; 469 } 470 if (!nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) { 471 return true; 472 } 473 } 474 } 475 return false; 476 } 477 478 private final SparseIntArray mRetriesLeftPerErrorCode = new SparseIntArray(); 479 resetErrorCodeRetries()480 public void resetErrorCodeRetries() { 481 requestLog(this, "resetErrorCodeRetries"); 482 483 String[] config = mPhone.getContext().getResources().getStringArray( 484 com.android.internal.R.array.config_cell_retries_per_error_code); 485 synchronized (mRetriesLeftPerErrorCode) { 486 mRetriesLeftPerErrorCode.clear(); 487 488 for (String c : config) { 489 String errorValue[] = c.split(","); 490 if (errorValue != null && errorValue.length == 2) { 491 int count = 0; 492 int errorCode = 0; 493 try { 494 errorCode = Integer.parseInt(errorValue[0]); 495 count = Integer.parseInt(errorValue[1]); 496 } catch (NumberFormatException e) { 497 log("Exception parsing config_retries_per_error_code: " + e); 498 continue; 499 } 500 if (count > 0 && errorCode > 0) { 501 mRetriesLeftPerErrorCode.put(errorCode, count); 502 } 503 } else { 504 log("Exception parsing config_retries_per_error_code: " + c); 505 } 506 } 507 } 508 } 509 restartOnError(int errorCode)510 public boolean restartOnError(int errorCode) { 511 boolean result = false; 512 int retriesLeft = 0; 513 synchronized(mRetriesLeftPerErrorCode) { 514 retriesLeft = mRetriesLeftPerErrorCode.get(errorCode); 515 switch (retriesLeft) { 516 case 0: { 517 // not set, never restart modem 518 break; 519 } 520 case 1: { 521 resetErrorCodeRetries(); 522 result = true; 523 break; 524 } 525 default: { 526 mRetriesLeftPerErrorCode.put(errorCode, retriesLeft - 1); 527 result = false; 528 } 529 } 530 } 531 requestLog(this, "restartOnError(" + errorCode + ") found " + retriesLeft 532 + " and returned " + result); 533 return result; 534 } 535 incAndGetConnectionGeneration()536 public int incAndGetConnectionGeneration() { 537 return mConnectionGeneration.incrementAndGet(); 538 } 539 getConnectionGeneration()540 public int getConnectionGeneration() { 541 return mConnectionGeneration.get(); 542 } 543 getRetryAfterDisconnectDelay()544 long getRetryAfterDisconnectDelay() { 545 return mRetryManager.getRetryAfterDisconnectDelay(); 546 } 547 548 /** 549 * Get APN type from the network request. 550 * 551 * @param nr The network request. 552 * @return The APN type. 553 */ getApnTypeFromNetworkRequest(NetworkRequest nr)554 public static @ApnType int getApnTypeFromNetworkRequest(NetworkRequest nr) { 555 // For now, ignore the bandwidth stuff 556 if (nr.getTransportTypes().length > 0 557 && !nr.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { 558 return ApnSetting.TYPE_NONE; 559 } 560 561 // in the near term just do 1-1 matches. 562 // TODO - actually try to match the set of capabilities 563 int apnType = ApnSetting.TYPE_NONE; 564 boolean error = false; 565 566 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { 567 apnType = ApnSetting.TYPE_DEFAULT; 568 } 569 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) { 570 if (apnType != ApnSetting.TYPE_NONE) error = true; 571 apnType = ApnSetting.TYPE_MMS; 572 } 573 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) { 574 if (apnType != ApnSetting.TYPE_NONE) error = true; 575 apnType = ApnSetting.TYPE_SUPL; 576 } 577 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) { 578 if (apnType != ApnSetting.TYPE_NONE) error = true; 579 apnType = ApnSetting.TYPE_DUN; 580 } 581 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) { 582 if (apnType != ApnSetting.TYPE_NONE) error = true; 583 apnType = ApnSetting.TYPE_FOTA; 584 } 585 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) { 586 if (apnType != ApnSetting.TYPE_NONE) error = true; 587 apnType = ApnSetting.TYPE_IMS; 588 } 589 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) { 590 if (apnType != ApnSetting.TYPE_NONE) error = true; 591 apnType = ApnSetting.TYPE_CBS; 592 } 593 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_IA)) { 594 if (apnType != ApnSetting.TYPE_NONE) error = true; 595 apnType = ApnSetting.TYPE_IA; 596 } 597 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) { 598 if (apnType != ApnSetting.TYPE_NONE) error = true; 599 apnType = ApnSetting.TYPE_EMERGENCY; 600 } 601 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_MCX)) { 602 if (apnType != ApnSetting.TYPE_NONE) error = true; 603 apnType = ApnSetting.TYPE_MCX; 604 } 605 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_XCAP)) { 606 if (apnType != ApnSetting.TYPE_NONE) error = true; 607 apnType = ApnSetting.TYPE_XCAP; 608 } 609 if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)) { 610 if (apnType != ApnSetting.TYPE_NONE) error = true; 611 apnType = ApnSetting.TYPE_ENTERPRISE; 612 } 613 if (error) { 614 // TODO: If this error condition is removed, the framework's handling of 615 // NET_CAPABILITY_NOT_RESTRICTED will need to be updated so requests for 616 // say FOTA and INTERNET are marked as restricted. This is not how 617 // NetworkCapabilities.maybeMarkCapabilitiesRestricted currently works. 618 Rlog.d(SLOG_TAG, "Multiple apn types specified in request - result is unspecified!"); 619 } 620 if (apnType == ApnSetting.TYPE_NONE) { 621 Rlog.d(SLOG_TAG, "Unsupported NetworkRequest in Telephony: nr=" + nr); 622 } 623 return apnType; 624 } 625 getNetworkRequests()626 public List<NetworkRequest> getNetworkRequests() { 627 synchronized (mRefCountLock) { 628 return new ArrayList<NetworkRequest>(mNetworkRequests); 629 } 630 } 631 632 @Override toString()633 public synchronized String toString() { 634 // We don't print mDataConnection because its recursive. 635 return "{mApnType=" + mApnType + " mState=" + getState() + " mWaitingApns={" 636 + mRetryManager.getWaitingApns() + " priority=" + mPriority + "}" 637 + " mApnSetting={" + mApnSetting 638 + "} mReason=" + mReason + " mDataEnabled=" + mDataEnabled + "}"; 639 } 640 log(String s)641 private void log(String s) { 642 if (DBG) { 643 Rlog.d(LOG_TAG, "[ApnContext:" + mApnType + "] " + s); 644 } 645 } 646 dump(FileDescriptor fd, PrintWriter printWriter, String[] args)647 public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { 648 final IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); 649 synchronized (mRefCountLock) { 650 pw.println(toString()); 651 if (mNetworkRequests.size() > 0) { 652 pw.println("NetworkRequests:"); 653 pw.increaseIndent(); 654 for (NetworkRequest nr : mNetworkRequests) { 655 pw.println(nr); 656 } 657 pw.decreaseIndent(); 658 } 659 pw.println("Historical APN state:"); 660 pw.increaseIndent(); 661 mStateLocalLog.dump(fd, pw, args); 662 pw.decreaseIndent(); 663 pw.println(mRetryManager); 664 pw.println("--------------------------"); 665 } 666 } 667 668 /** Dumps the ApnContext local log. */ dumpLocalLog(FileDescriptor fd, PrintWriter printWriter, String[] args)669 public static void dumpLocalLog(FileDescriptor fd, PrintWriter printWriter, String[] args) { 670 printWriter.println("Local log:"); 671 synchronized (sLocalLog) { 672 sLocalLog.dump(fd, printWriter, args); 673 } 674 } 675 } 676