• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.KeepalivePacketData;
22 import android.net.LinkProperties;
23 import android.net.NattKeepalivePacketData;
24 import android.net.NetworkAgent;
25 import android.net.NetworkAgentConfig;
26 import android.net.NetworkCapabilities;
27 import android.net.NetworkProvider;
28 import android.net.QosFilter;
29 import android.net.QosSessionAttributes;
30 import android.net.SocketKeepalive;
31 import android.net.Uri;
32 import android.os.Handler;
33 import android.os.Message;
34 import android.telephony.AccessNetworkConstants;
35 import android.telephony.AccessNetworkConstants.TransportType;
36 import android.telephony.Annotation.NetworkType;
37 import android.telephony.AnomalyReporter;
38 import android.telephony.NetworkRegistrationInfo;
39 import android.telephony.ServiceState;
40 import android.telephony.TelephonyManager;
41 import android.telephony.data.QosBearerSession;
42 import android.util.LocalLog;
43 import android.util.SparseArray;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.telephony.DctConstants;
47 import com.android.internal.telephony.Phone;
48 import com.android.internal.telephony.RILConstants;
49 import com.android.internal.telephony.SlidingWindowEventCounter;
50 import com.android.internal.telephony.data.KeepaliveStatus;
51 import com.android.internal.telephony.data.NotifyQosSessionInterface;
52 import com.android.internal.telephony.data.QosCallbackTracker;
53 import com.android.internal.telephony.metrics.TelephonyMetrics;
54 import com.android.internal.util.IndentingPrintWriter;
55 import com.android.telephony.Rlog;
56 
57 import java.io.FileDescriptor;
58 import java.io.PrintWriter;
59 import java.net.InetAddress;
60 import java.time.Duration;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Objects;
64 import java.util.UUID;
65 import java.util.concurrent.ConcurrentHashMap;
66 import java.util.concurrent.Executor;
67 import java.util.concurrent.Executors;
68 import java.util.concurrent.TimeUnit;
69 
70 /**
71  * This class represents a network agent which is communication channel between
72  * {@link DataConnection} and {@link com.android.server.ConnectivityService}. The agent is
73  * created when data connection enters {@link DataConnection.DcActiveState} until it exits that
74  * state.
75  *
76  * Note that in IWLAN handover scenario, this agent could be transferred to the new
77  * {@link DataConnection} so for a short window of time this object might be accessed by two
78  * different {@link DataConnection}. Thus each method in this class needs to be synchronized.
79  */
80 public class DcNetworkAgent extends NetworkAgent implements NotifyQosSessionInterface {
81     private final String mTag;
82 
83     private final int mId;
84 
85     private final Phone mPhone;
86 
87     private final Handler mHandler;
88 
89     private int mTransportType;
90 
91     private NetworkCapabilities mNetworkCapabilities;
92 
93     public final DcKeepaliveTracker keepaliveTracker = new DcKeepaliveTracker();
94 
95     private final QosCallbackTracker mQosCallbackTracker;
96 
97     private final Executor mQosCallbackExecutor = Executors.newSingleThreadExecutor();
98 
99     private DataConnection mDataConnection;
100 
101     private final LocalLog mNetCapsLocalLog = new LocalLog(32);
102 
103     // For interface duplicate detection. Key is the net id, value is the interface name in string.
104     private static Map<Integer, String> sInterfaceNames = new ConcurrentHashMap<>();
105 
106     private static final long NETWORK_UNWANTED_ANOMALY_WINDOW_MS = TimeUnit.MINUTES.toMillis(5);
107     private static final int NETWORK_UNWANTED_ANOMALY_NUM_OCCURRENCES =  12;
108 
109     private static final int EVENT_UNWANTED_TIMEOUT = 1;
110 
111     @VisibleForTesting
DcNetworkAgent(DataConnection dc, Phone phone, int score, NetworkAgentConfig config, NetworkProvider networkProvider, int transportType)112     public DcNetworkAgent(DataConnection dc, Phone phone, int score, NetworkAgentConfig config,
113             NetworkProvider networkProvider, int transportType) {
114         super(phone.getContext(), dc.getHandler().getLooper(), "DcNetworkAgent",
115                 dc.getNetworkCapabilities(), dc.getLinkProperties(), score, config,
116                 networkProvider);
117         register();
118         mId = getNetwork().getNetId();
119         mTag = "DcNetworkAgent" + "-" + mId;
120         mPhone = phone;
121         mHandler = new Handler(dc.getHandler().getLooper()) {
122             @Override
123             public void handleMessage(Message msg) {
124                 if (msg.what == EVENT_UNWANTED_TIMEOUT) {
125                     loge("onNetworkUnwanted timed out. Perform silent de-register.");
126                     logd("Unregister from connectivity service. " + sInterfaceNames.get(mId)
127                             + " removed.");
128                     sInterfaceNames.remove(mId);
129                     DcNetworkAgent.this.unregister();
130                 }
131             }
132         };
133         mNetworkCapabilities = dc.getNetworkCapabilities();
134         mTransportType = transportType;
135         mDataConnection = dc;
136         if (dc.getLinkProperties() != null) {
137             checkDuplicateInterface(mId, dc.getLinkProperties().getInterfaceName());
138             logd("created for data connection " + dc.getName() + ", "
139                     + dc.getLinkProperties().getInterfaceName());
140         } else {
141             loge("The connection does not have a valid link properties.");
142         }
143         mQosCallbackTracker = new QosCallbackTracker(this, mPhone);
144     }
145 
getNetworkType()146     private @NetworkType int getNetworkType() {
147         ServiceState ss = mPhone.getServiceState();
148         int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
149 
150         NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
151                 NetworkRegistrationInfo.DOMAIN_PS, mTransportType);
152         if (nri != null) {
153             networkType = nri.getAccessNetworkTechnology();
154         }
155 
156         return networkType;
157     }
158 
checkDuplicateInterface(int netId, @Nullable String interfaceName)159     private void checkDuplicateInterface(int netId, @Nullable String interfaceName) {
160         for (Map.Entry<Integer, String> entry: sInterfaceNames.entrySet()) {
161             if (Objects.equals(interfaceName, entry.getValue())) {
162                 String message = "Duplicate interface " + interfaceName
163                         + " is detected. DcNetworkAgent-" + entry.getKey()
164                         + " already used this interface name.";
165                 loge(message);
166                 // Using fixed UUID to avoid duplicate bugreport notification
167                 AnomalyReporter.reportAnomaly(
168                         UUID.fromString("02f3d3f6-4613-4415-b6cb-8d92c8a938a6"),
169                         message, mPhone.getCarrierId());
170                 return;
171             }
172         }
173         sInterfaceNames.put(netId, interfaceName);
174     }
175 
176     /**
177      * @return The tag
178      */
getTag()179     String getTag() {
180         return mTag;
181     }
182 
183     /**
184      * Set the data connection that owns this network agent.
185      *
186      * @param dc Data connection owning this network agent.
187      * @param transportType Transport that this data connection is on.
188      */
acquireOwnership(@onNull DataConnection dc, @TransportType int transportType)189     public synchronized void acquireOwnership(@NonNull DataConnection dc,
190                                               @TransportType int transportType) {
191         mDataConnection = dc;
192         mTransportType = transportType;
193         logd(dc.getName() + " acquired the ownership of this agent.");
194     }
195 
196     /**
197      * Release the ownership of network agent.
198      */
releaseOwnership(DataConnection dc)199     public synchronized void releaseOwnership(DataConnection dc) {
200         if (mDataConnection == null) {
201             loge("releaseOwnership called on no-owner DcNetworkAgent!");
202             return;
203         } else if (mDataConnection != dc) {
204             loge("releaseOwnership: This agent belongs to "
205                     + mDataConnection.getName() + ", ignored the request from " + dc.getName());
206             return;
207         }
208         logd("Data connection " + mDataConnection.getName() + " released the ownership.");
209         mDataConnection = null;
210     }
211 
212     /**
213      * @return The data connection that owns this agent
214      */
getDataConnection()215     public synchronized DataConnection getDataConnection() {
216         return mDataConnection;
217     }
218 
219     private static final SlidingWindowEventCounter sNetworkUnwantedCounter =
220             new SlidingWindowEventCounter(NETWORK_UNWANTED_ANOMALY_WINDOW_MS,
221                     NETWORK_UNWANTED_ANOMALY_NUM_OCCURRENCES);
222 
223     @Override
onNetworkUnwanted()224     public synchronized void onNetworkUnwanted() {
225         mHandler.sendEmptyMessageDelayed(EVENT_UNWANTED_TIMEOUT, TimeUnit.SECONDS.toMillis(30));
226         trackNetworkUnwanted();
227         if (mDataConnection == null) {
228             loge("onNetworkUnwanted found called on no-owner DcNetworkAgent!");
229             return;
230         }
231 
232         logd("onNetworkUnwanted called. Now tear down the data connection "
233                 + mDataConnection.getName());
234         mDataConnection.tearDownAll(Phone.REASON_RELEASED_BY_CONNECTIVITY_SERVICE,
235                 DcTracker.RELEASE_TYPE_DETACH, null);
236     }
237 
238     /**
239      * There have been several bugs where a RECONNECT loop kicks off where a DataConnection
240      * connects to the Network, ConnectivityService indicates that the Network is unwanted,
241      * and then the DataConnection reconnects.  By the time we get the bug report it's too late
242      * because there have already been hundreds of RECONNECTS.  This is meant to capture the issue
243      * when it first starts.
244      *
245      * The unwanted counter is configured to only take an anomaly report in extreme cases.
246      * This is to avoid having the anomaly message show up on several devices.
247      *
248      * This is directly related to b/175845538.  But, there have been several other occurrences of
249      * this issue.
250      */
trackNetworkUnwanted()251     private void trackNetworkUnwanted() {
252         if (sNetworkUnwantedCounter.addOccurrence()) {
253             AnomalyReporter.reportAnomaly(
254                     UUID.fromString("3f578b5c-64e9-11eb-ae93-0242ac130002"),
255                     "Network Unwanted called 12 times in 5 minutes.", mPhone.getCarrierId());
256         }
257     }
258 
259     @Override
onBandwidthUpdateRequested()260     public synchronized void onBandwidthUpdateRequested() {
261         if (mDataConnection == null) {
262             loge("onBandwidthUpdateRequested called on no-owner DcNetworkAgent!");
263             return;
264         }
265 
266         if (mPhone.getLceStatus() == RILConstants.LCE_ACTIVE     // active LCE service
267                 && mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
268             mPhone.mCi.pullLceData(mDataConnection.obtainMessage(
269                     DataConnection.EVENT_BW_REFRESH_RESPONSE));
270         }
271     }
272 
273     @Override
onValidationStatus(int status, Uri redirectUri)274     public synchronized void onValidationStatus(int status, Uri redirectUri) {
275         if (mDataConnection == null) {
276             loge("onValidationStatus called on no-owner DcNetworkAgent!");
277             return;
278         }
279 
280         logd("validation status: " + status + " with redirection URL: " + redirectUri);
281         DcTracker dct = mPhone.getDcTracker(mTransportType);
282         if (dct != null) {
283             Message msg = dct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
284                     status, mDataConnection.getCid(), redirectUri.toString());
285             msg.sendToTarget();
286         }
287     }
288 
isOwned(DataConnection dc, String reason)289     private synchronized boolean isOwned(DataConnection dc, String reason) {
290         if (mDataConnection == null) {
291             loge(reason + " called on no-owner DcNetworkAgent!");
292             return false;
293         } else if (mDataConnection != dc) {
294             loge(reason + ": This agent belongs to "
295                     + mDataConnection.getName() + ", ignored the request from " + dc.getName());
296             return false;
297         }
298         return true;
299     }
300 
301     /**
302      * Update the legacy sub type (i.e. data network type).
303      *
304      * @param dc The data connection that invokes this method.
305      */
updateLegacySubtype(DataConnection dc)306     public synchronized void updateLegacySubtype(DataConnection dc) {
307         if (!isOwned(dc, "updateLegacySubtype")) return;
308 
309         int networkType = getNetworkType();
310         setLegacySubtype(networkType, TelephonyManager.getNetworkTypeName(networkType));
311     }
312 
313     /**
314      * Set the network capabilities.
315      *
316      * @param networkCapabilities The network capabilities.
317      * @param dc The data connection that invokes this method.
318      */
sendNetworkCapabilities(NetworkCapabilities networkCapabilities, DataConnection dc)319     public synchronized void sendNetworkCapabilities(NetworkCapabilities networkCapabilities,
320                                                      DataConnection dc) {
321         if (!isOwned(dc, "sendNetworkCapabilities")) return;
322 
323         if (!networkCapabilities.equals(mNetworkCapabilities)) {
324             String logStr = "Changed from " + mNetworkCapabilities + " to "
325                     + networkCapabilities + ", Data RAT="
326                     + mPhone.getServiceState().getRilDataRadioTechnology()
327                     + ", dc=" + mDataConnection.getName();
328             logd(logStr);
329             mNetCapsLocalLog.log(logStr);
330             if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
331                 // only log metrics for DataConnection with NET_CAPABILITY_INTERNET
332                 if (mNetworkCapabilities == null
333                         || networkCapabilities.hasCapability(
334                                 NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
335                         != mNetworkCapabilities.hasCapability(
336                                 NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)) {
337                     TelephonyMetrics.getInstance().writeNetworkCapabilitiesChangedEvent(
338                             mPhone.getPhoneId(), networkCapabilities);
339                 }
340             }
341             mNetworkCapabilities = networkCapabilities;
342         }
343         sendNetworkCapabilities(networkCapabilities);
344     }
345 
346     /**
347      * Set the link properties
348      *
349      * @param linkProperties The link properties
350      * @param dc The data connection that invokes this method.
351      */
sendLinkProperties(@onNull LinkProperties linkProperties, DataConnection dc)352     public synchronized void sendLinkProperties(@NonNull LinkProperties linkProperties,
353                                                 DataConnection dc) {
354         if (!isOwned(dc, "sendLinkProperties")) return;
355 
356         sInterfaceNames.put(mId, dc.getLinkProperties().getInterfaceName());
357         sendLinkProperties(linkProperties);
358     }
359 
360     /**
361      * Set the network score.
362      *
363      * @param score The network score.
364      * @param dc The data connection that invokes this method.
365      */
sendNetworkScore(int score, DataConnection dc)366     public synchronized void sendNetworkScore(int score, DataConnection dc) {
367         if (!isOwned(dc, "sendNetworkScore")) return;
368         sendNetworkScore(score);
369     }
370 
371     /**
372      * Unregister the network agent from connectivity service.
373      *
374      * @param dc The data connection that invokes this method.
375      */
unregister(DataConnection dc)376     public synchronized void unregister(DataConnection dc) {
377         if (!isOwned(dc, "unregister")) return;
378 
379         mHandler.removeMessages(EVENT_UNWANTED_TIMEOUT);
380         logd("Unregister from connectivity service. " + sInterfaceNames.get(mId) + " removed.");
381         sInterfaceNames.remove(mId);
382         super.unregister();
383     }
384 
385     @Override
onStartSocketKeepalive(int slot, @NonNull Duration interval, @NonNull KeepalivePacketData packet)386     public synchronized void onStartSocketKeepalive(int slot, @NonNull Duration interval,
387             @NonNull KeepalivePacketData packet) {
388         if (mDataConnection == null) {
389             loge("onStartSocketKeepalive called on no-owner DcNetworkAgent!");
390             return;
391         }
392 
393         if (packet instanceof NattKeepalivePacketData) {
394             mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_START_REQUEST,
395                     slot, (int) interval.getSeconds(), packet).sendToTarget();
396         } else {
397             sendSocketKeepaliveEvent(slot, SocketKeepalive.ERROR_UNSUPPORTED);
398         }
399     }
400 
401     @Override
onStopSocketKeepalive(int slot)402     public synchronized void onStopSocketKeepalive(int slot) {
403         if (mDataConnection == null) {
404             loge("onStopSocketKeepalive called on no-owner DcNetworkAgent!");
405             return;
406         }
407 
408         mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_STOP_REQUEST, slot)
409                 .sendToTarget();
410     }
411 
412     @Override
onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter)413     public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) {
414         mQosCallbackExecutor.execute(() -> mQosCallbackTracker.addFilter(qosCallbackId,
415               new QosCallbackTracker.IFilter() {
416                   @Override
417                   public boolean matchesLocalAddress(
418                           InetAddress address, int startPort, int endPort) {
419                       return filter.matchesLocalAddress(address, startPort, endPort);
420                   }
421 
422                   @Override
423                   public boolean matchesRemoteAddress(
424                           InetAddress address, int startPort, int endPort) {
425                       return filter.matchesRemoteAddress(address, startPort, endPort);
426                   }
427               }));
428     }
429 
430     @Override
onQosCallbackUnregistered(final int qosCallbackId)431     public void onQosCallbackUnregistered(final int qosCallbackId) {
432         mQosCallbackExecutor.execute(() -> mQosCallbackTracker.removeFilter(qosCallbackId));
433     }
434 
updateQosBearerSessions(final List<QosBearerSession> qosBearerSessions)435     void updateQosBearerSessions(final List<QosBearerSession> qosBearerSessions) {
436         mQosCallbackExecutor.execute(() -> mQosCallbackTracker.updateSessions(qosBearerSessions));
437     }
438 
439     @Override
notifyQosSessionAvailable(final int qosCallbackId, final int sessionId, @NonNull final QosSessionAttributes attributes)440     public void notifyQosSessionAvailable(final int qosCallbackId, final int sessionId,
441             @NonNull final QosSessionAttributes attributes) {
442         super.sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
443     }
444 
445     @Override
notifyQosSessionLost(final int qosCallbackId, final int sessionId, final int qosSessionType)446     public void notifyQosSessionLost(final int qosCallbackId,
447             final int sessionId, final int qosSessionType) {
448         super.sendQosSessionLost(qosCallbackId, sessionId, qosSessionType);
449     }
450 
451     @Override
toString()452     public String toString() {
453         return "DcNetworkAgent-"
454                 + mId
455                 + " mDataConnection="
456                 + ((mDataConnection != null) ? mDataConnection.getName() : null)
457                 + " mTransportType="
458                 + AccessNetworkConstants.transportTypeToString(mTransportType)
459                 + " " + ((mDataConnection != null) ? mDataConnection.getLinkProperties() : null)
460                 + " mNetworkCapabilities=" + mNetworkCapabilities;
461     }
462 
463     /**
464      * Dump the state of transport manager
465      *
466      * @param fd File descriptor
467      * @param printWriter Print writer
468      * @param args Arguments
469      */
dump(FileDescriptor fd, PrintWriter printWriter, String[] args)470     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
471         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
472         pw.println(toString());
473         pw.increaseIndent();
474         pw.println("Net caps logs:");
475         mNetCapsLocalLog.dump(fd, pw, args);
476         pw.decreaseIndent();
477     }
478 
479     /**
480      * Log with debug level
481      *
482      * @param s is string log
483      */
logd(String s)484     private void logd(String s) {
485         Rlog.d(mTag, s);
486     }
487 
488     /**
489      * Log with error level
490      *
491      * @param s is string log
492      */
loge(String s)493     private void loge(String s) {
494         Rlog.e(mTag, s);
495     }
496 
497     class DcKeepaliveTracker {
498         private class KeepaliveRecord {
499             public int slotId;
500             public int currentStatus;
501 
KeepaliveRecord(int slotId, int status)502             KeepaliveRecord(int slotId, int status) {
503                 this.slotId = slotId;
504                 this.currentStatus = status;
505             }
506         }
507 
508         private final SparseArray<KeepaliveRecord> mKeepalives = new SparseArray();
509 
getHandleForSlot(int slotId)510         int getHandleForSlot(int slotId) {
511             for (int i = 0; i < mKeepalives.size(); i++) {
512                 KeepaliveRecord kr = mKeepalives.valueAt(i);
513                 if (kr.slotId == slotId) return mKeepalives.keyAt(i);
514             }
515             return -1;
516         }
517 
keepaliveStatusErrorToPacketKeepaliveError(int error)518         int keepaliveStatusErrorToPacketKeepaliveError(int error) {
519             switch(error) {
520                 case KeepaliveStatus.ERROR_NONE:
521                     return SocketKeepalive.SUCCESS;
522                 case KeepaliveStatus.ERROR_UNSUPPORTED:
523                     return SocketKeepalive.ERROR_UNSUPPORTED;
524                 case KeepaliveStatus.ERROR_NO_RESOURCES:
525                     return SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES;
526                 case KeepaliveStatus.ERROR_UNKNOWN:
527                 default:
528                     return SocketKeepalive.ERROR_HARDWARE_ERROR;
529             }
530         }
531 
handleKeepaliveStarted(final int slot, KeepaliveStatus ks)532         void handleKeepaliveStarted(final int slot, KeepaliveStatus ks) {
533             switch (ks.statusCode) {
534                 case KeepaliveStatus.STATUS_INACTIVE:
535                     DcNetworkAgent.this.sendSocketKeepaliveEvent(slot,
536                             keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
537                     break;
538                 case KeepaliveStatus.STATUS_ACTIVE:
539                     DcNetworkAgent.this.sendSocketKeepaliveEvent(
540                             slot, SocketKeepalive.SUCCESS);
541                     // fall through to add record
542                 case KeepaliveStatus.STATUS_PENDING:
543                     logd("Adding keepalive handle="
544                             + ks.sessionHandle + " slot = " + slot);
545                     mKeepalives.put(ks.sessionHandle,
546                             new KeepaliveRecord(
547                                     slot, ks.statusCode));
548                     break;
549                 default:
550                     logd("Invalid KeepaliveStatus Code: " + ks.statusCode);
551                     break;
552             }
553         }
554 
handleKeepaliveStatus(KeepaliveStatus ks)555         void handleKeepaliveStatus(KeepaliveStatus ks) {
556             final KeepaliveRecord kr;
557             kr = mKeepalives.get(ks.sessionHandle);
558 
559             if (kr == null) {
560                 // If there is no slot for the session handle, we received an event
561                 // for a different data connection. This is not an error because the
562                 // keepalive session events are broadcast to all listeners.
563                 loge("Discarding keepalive event for different data connection:" + ks);
564                 return;
565             }
566             // Switch on the current state, to see what we do with the status update
567             switch (kr.currentStatus) {
568                 case KeepaliveStatus.STATUS_INACTIVE:
569                     logd("Inactive Keepalive received status!");
570                     DcNetworkAgent.this.sendSocketKeepaliveEvent(
571                             kr.slotId, SocketKeepalive.ERROR_HARDWARE_ERROR);
572                     break;
573                 case KeepaliveStatus.STATUS_PENDING:
574                     switch (ks.statusCode) {
575                         case KeepaliveStatus.STATUS_INACTIVE:
576                             DcNetworkAgent.this.sendSocketKeepaliveEvent(kr.slotId,
577                                     keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
578                             kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
579                             mKeepalives.remove(ks.sessionHandle);
580                             break;
581                         case KeepaliveStatus.STATUS_ACTIVE:
582                             logd("Pending Keepalive received active status!");
583                             kr.currentStatus = KeepaliveStatus.STATUS_ACTIVE;
584                             DcNetworkAgent.this.sendSocketKeepaliveEvent(
585                                     kr.slotId, SocketKeepalive.SUCCESS);
586                             break;
587                         case KeepaliveStatus.STATUS_PENDING:
588                             loge("Invalid unsolicied Keepalive Pending Status!");
589                             break;
590                         default:
591                             loge("Invalid Keepalive Status received, " + ks.statusCode);
592                     }
593                     break;
594                 case KeepaliveStatus.STATUS_ACTIVE:
595                     switch (ks.statusCode) {
596                         case KeepaliveStatus.STATUS_INACTIVE:
597                             logd("Keepalive received stopped status!");
598                             DcNetworkAgent.this.sendSocketKeepaliveEvent(
599                                     kr.slotId, SocketKeepalive.SUCCESS);
600 
601                             kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
602                             mKeepalives.remove(ks.sessionHandle);
603                             break;
604                         case KeepaliveStatus.STATUS_PENDING:
605                         case KeepaliveStatus.STATUS_ACTIVE:
606                             loge("Active Keepalive received invalid status!");
607                             break;
608                         default:
609                             loge("Invalid Keepalive Status received, " + ks.statusCode);
610                     }
611                     break;
612                 default:
613                     loge("Invalid Keepalive Status received, " + kr.currentStatus);
614             }
615         }
616     }
617 }
618