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