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