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