• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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