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