• 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 LocalLog mLocalLog = new LocalLog(150);
398     private final ArraySet<NetworkRequest> mNetworkRequests = new ArraySet<>();
399     private final LocalLog mStateLocalLog = new LocalLog(50);
400 
requestLog(String str)401     public void requestLog(String str) {
402         synchronized (mLocalLog) {
403             mLocalLog.log(str);
404         }
405     }
406 
407     /**
408      * Request a network
409      *
410      * @param networkRequest Network request from clients
411      * @param type The request type
412      * @param onHandoverCompleteMsg When request type is handover, this message will be sent when
413      * handover is completed. For normal request, this should be null.
414      */
requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type, Message onHandoverCompleteMsg)415     public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type,
416             Message onHandoverCompleteMsg) {
417         synchronized (mRefCountLock) {
418             mNetworkRequests.add(networkRequest);
419             logl("requestNetwork for " + networkRequest + ", type="
420                     + DcTracker.requestTypeToString(type));
421             mDcTracker.enableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type,
422                     onHandoverCompleteMsg);
423             if (mDataConnection != null) {
424                 // New network request added. Should re-evaluate properties of
425                 // the data connection. For example, the score may change.
426                 mDataConnection.reevaluateDataConnectionProperties();
427             }
428         }
429     }
430 
releaseNetwork(NetworkRequest networkRequest, @ReleaseNetworkType int type)431     public void releaseNetwork(NetworkRequest networkRequest, @ReleaseNetworkType int type) {
432         synchronized (mRefCountLock) {
433             if (mNetworkRequests.contains(networkRequest)) {
434                 mNetworkRequests.remove(networkRequest);
435                 if (mDataConnection != null) {
436                     // New network request added. Should re-evaluate properties of
437                     // the data connection. For example, the score may change.
438                     mDataConnection.reevaluateDataConnectionProperties();
439                 }
440                 logl("releaseNetwork left with " + mNetworkRequests.size()
441                         + " requests.");
442                 if (mNetworkRequests.size() == 0
443                         || type == DcTracker.RELEASE_TYPE_DETACH
444                         || type == DcTracker.RELEASE_TYPE_HANDOVER) {
445                     mDcTracker.disableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type);
446                 }
447             }
448         }
449     }
450 
451     /**
452      * @param excludeDun True if excluding requests that have DUN capability
453      * @return True if the attached network requests contain restricted capability.
454      */
hasRestrictedRequests(boolean excludeDun)455     public boolean hasRestrictedRequests(boolean excludeDun) {
456         synchronized (mRefCountLock) {
457             for (NetworkRequest nr : mNetworkRequests) {
458                 if (excludeDun &&
459                         nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
460                     continue;
461                 }
462                 if (!nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
463                     return true;
464                 }
465             }
466         }
467         return false;
468     }
469 
470     private final SparseIntArray mRetriesLeftPerErrorCode = new SparseIntArray();
471 
resetErrorCodeRetries()472     public void resetErrorCodeRetries() {
473         logl("ApnContext.resetErrorCodeRetries");
474 
475         String[] config = mPhone.getContext().getResources().getStringArray(
476                 com.android.internal.R.array.config_cell_retries_per_error_code);
477         synchronized (mRetriesLeftPerErrorCode) {
478             mRetriesLeftPerErrorCode.clear();
479 
480             for (String c : config) {
481                 String errorValue[] = c.split(",");
482                 if (errorValue != null && errorValue.length == 2) {
483                     int count = 0;
484                     int errorCode = 0;
485                     try {
486                         errorCode = Integer.parseInt(errorValue[0]);
487                         count = Integer.parseInt(errorValue[1]);
488                     } catch (NumberFormatException e) {
489                         log("Exception parsing config_retries_per_error_code: " + e);
490                         continue;
491                     }
492                     if (count > 0 && errorCode > 0) {
493                         mRetriesLeftPerErrorCode.put(errorCode, count);
494                     }
495                 } else {
496                     log("Exception parsing config_retries_per_error_code: " + c);
497                 }
498             }
499         }
500     }
501 
restartOnError(int errorCode)502     public boolean restartOnError(int errorCode) {
503         boolean result = false;
504         int retriesLeft = 0;
505         synchronized(mRetriesLeftPerErrorCode) {
506             retriesLeft = mRetriesLeftPerErrorCode.get(errorCode);
507             switch (retriesLeft) {
508                 case 0: {
509                     // not set, never restart modem
510                     break;
511                 }
512                 case 1: {
513                     resetErrorCodeRetries();
514                     result = true;
515                     break;
516                 }
517                 default: {
518                     mRetriesLeftPerErrorCode.put(errorCode, retriesLeft - 1);
519                     result = false;
520                 }
521             }
522         }
523         logl("ApnContext.restartOnError(" + errorCode + ") found " + retriesLeft
524                 + " and returned " + result);
525         return result;
526     }
527 
incAndGetConnectionGeneration()528     public int incAndGetConnectionGeneration() {
529         return mConnectionGeneration.incrementAndGet();
530     }
531 
getConnectionGeneration()532     public int getConnectionGeneration() {
533         return mConnectionGeneration.get();
534     }
535 
getRetryAfterDisconnectDelay()536     long getRetryAfterDisconnectDelay() {
537         return mRetryManager.getRetryAfterDisconnectDelay();
538     }
539 
getApnTypeFromNetworkRequest(NetworkRequest nr)540     static @ApnType int getApnTypeFromNetworkRequest(NetworkRequest nr) {
541         // For now, ignore the bandwidth stuff
542         if (nr.getTransportTypes().length > 0
543                 && !nr.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
544             return ApnSetting.TYPE_NONE;
545         }
546 
547         // in the near term just do 1-1 matches.
548         // TODO - actually try to match the set of capabilities
549         int apnType = ApnSetting.TYPE_NONE;
550         boolean error = false;
551 
552         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
553             apnType = ApnSetting.TYPE_DEFAULT;
554         }
555         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
556             if (apnType != ApnSetting.TYPE_NONE) error = true;
557             apnType = ApnSetting.TYPE_MMS;
558         }
559         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
560             if (apnType != ApnSetting.TYPE_NONE) error = true;
561             apnType = ApnSetting.TYPE_SUPL;
562         }
563         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
564             if (apnType != ApnSetting.TYPE_NONE) error = true;
565             apnType = ApnSetting.TYPE_DUN;
566         }
567         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
568             if (apnType != ApnSetting.TYPE_NONE) error = true;
569             apnType = ApnSetting.TYPE_FOTA;
570         }
571         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
572             if (apnType != ApnSetting.TYPE_NONE) error = true;
573             apnType = ApnSetting.TYPE_IMS;
574         }
575         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
576             if (apnType != ApnSetting.TYPE_NONE) error = true;
577             apnType = ApnSetting.TYPE_CBS;
578         }
579         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_IA)) {
580             if (apnType != ApnSetting.TYPE_NONE) error = true;
581             apnType = ApnSetting.TYPE_IA;
582         }
583         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
584             if (apnType != ApnSetting.TYPE_NONE) error = true;
585             apnType = ApnSetting.TYPE_EMERGENCY;
586         }
587         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_MCX)) {
588             if (apnType != ApnSetting.TYPE_NONE) error = true;
589             apnType = ApnSetting.TYPE_MCX;
590         }
591         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_XCAP)) {
592             if (apnType != ApnSetting.TYPE_NONE) error = true;
593             apnType = ApnSetting.TYPE_XCAP;
594         }
595         if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)) {
596             if (apnType != ApnSetting.TYPE_NONE) error = true;
597             apnType = ApnSetting.TYPE_ENTERPRISE;
598         }
599         if (error) {
600             // TODO: If this error condition is removed, the framework's handling of
601             // NET_CAPABILITY_NOT_RESTRICTED will need to be updated so requests for
602             // say FOTA and INTERNET are marked as restricted.  This is not how
603             // NetworkCapabilities.maybeMarkCapabilitiesRestricted currently works.
604             Rlog.d(SLOG_TAG, "Multiple apn types specified in request - result is unspecified!");
605         }
606         if (apnType == ApnSetting.TYPE_NONE) {
607             Rlog.d(SLOG_TAG, "Unsupported NetworkRequest in Telephony: nr=" + nr);
608         }
609         return apnType;
610     }
611 
getNetworkRequests()612     public List<NetworkRequest> getNetworkRequests() {
613         synchronized (mRefCountLock) {
614             return new ArrayList<NetworkRequest>(mNetworkRequests);
615         }
616     }
617 
618     @Override
toString()619     public synchronized String toString() {
620         // We don't print mDataConnection because its recursive.
621         return "{mApnType=" + mApnType + " mState=" + getState() + " mWaitingApns={"
622                     + mRetryManager.getWaitingApns() + " priority=" + mPriority + "}"
623                     + " mApnSetting={" + mApnSetting
624                     + "} mReason=" + mReason + " mDataEnabled=" + mDataEnabled + "}";
625     }
626 
log(String s)627     private void log(String s) {
628         if (DBG) {
629             Rlog.d(LOG_TAG, "[ApnContext:" + mApnType + "] " + s);
630         }
631     }
632 
logl(String s)633     private void logl(String s) {
634         log(s);
635         mLocalLog.log(s);
636     }
637 
dump(FileDescriptor fd, PrintWriter printWriter, String[] args)638     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
639         final IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
640         synchronized (mRefCountLock) {
641             pw.println(toString());
642             if (mNetworkRequests.size() > 0) {
643                 pw.println("NetworkRequests:");
644                 pw.increaseIndent();
645                 for (NetworkRequest nr : mNetworkRequests) {
646                     pw.println(nr);
647                 }
648                 pw.decreaseIndent();
649             }
650             pw.increaseIndent();
651             pw.println("-----");
652             pw.println("Local log:");
653             mLocalLog.dump(fd, pw, args);
654             pw.println("-----");
655             pw.decreaseIndent();
656             pw.println("Historical APN state:");
657             pw.increaseIndent();
658             mStateLocalLog.dump(fd, pw, args);
659             pw.decreaseIndent();
660             pw.println(mRetryManager);
661             pw.println("--------------------------");
662         }
663     }
664 }
665