1 /* 2 * Copyright (C) 2022 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.telephony.qns; 18 19 import static com.android.telephony.qns.QnsConstants.INVALID_ID; 20 21 import android.annotation.NonNull; 22 import android.net.NetworkCapabilities; 23 import android.os.Handler; 24 import android.os.HandlerThread; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.telephony.AccessNetworkConstants; 28 import android.telephony.Annotation; 29 import android.telephony.CallQuality; 30 import android.telephony.CallState; 31 import android.telephony.PreciseCallState; 32 import android.telephony.PreciseDataConnectionState; 33 import android.telephony.TelephonyManager; 34 import android.telephony.ims.ImsCallProfile; 35 import android.telephony.ims.MediaQualityStatus; 36 import android.util.Log; 37 import android.util.SparseArray; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 import java.util.function.Consumer; 44 45 /** 46 * Tracking IMS Call status and update call type changed event to ANE. 47 */ 48 public class QnsCallStatusTracker { 49 private final String mLogTag; 50 private QnsTelephonyListener mTelephonyListener; 51 private QnsCarrierConfigManager mConfigManager; 52 private List<CallState> mCallStates = new ArrayList<>(); 53 private QnsRegistrant mCallTypeChangedEventListener; 54 private QnsRegistrant mEmergencyCallTypeChangedEventListener; 55 private final QnsTimer mQnsTimer; 56 private int mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE; 57 private int mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE; 58 private boolean mEmergencyOverIms; 59 private ActiveCallTracker mActiveCallTracker; 60 private Consumer<List<CallState>> mCallStatesConsumer = 61 callStateList -> updateCallState(callStateList); 62 private Consumer<Integer> mSrvccStateConsumer = state -> onSrvccStateChangedInternal(state); 63 private Consumer<MediaQualityStatus> mMediaQualityStatusConsumer = 64 status -> mActiveCallTracker.onMediaQualityStatusChanged(status); 65 66 static class CallQualityBlock { 67 int mUpLinkLevel; 68 int mDownLinkLevel; 69 long mCreatedElapsedTime; 70 long mDurationMillis; CallQualityBlock(int uplinkLevel, int downLinkLevel, long createdElapsedTime)71 CallQualityBlock(int uplinkLevel, int downLinkLevel, long createdElapsedTime) { 72 mUpLinkLevel = uplinkLevel; 73 mDownLinkLevel = downLinkLevel; 74 mCreatedElapsedTime = createdElapsedTime; 75 } 76 getUpLinkQualityVolume()77 long getUpLinkQualityVolume() { 78 if (mDurationMillis > 0) { 79 return mUpLinkLevel * mDurationMillis; 80 } else { 81 long now = QnsUtils.getSystemElapsedRealTime(); 82 return (now - mCreatedElapsedTime) * mUpLinkLevel; 83 } 84 } 85 getDownLinkQualityVolume()86 long getDownLinkQualityVolume() { 87 if (mDurationMillis > 0) { 88 return mDownLinkLevel * mDurationMillis; 89 } else { 90 long now = QnsUtils.getSystemElapsedRealTime(); 91 return (now - mCreatedElapsedTime) * mDownLinkLevel; 92 } 93 } 94 } 95 96 class ActiveCallTracker { 97 private static final int EVENT_DATA_CONNECTION_STATUS_CHANGED = 3300; 98 99 @QnsConstants.QnsCallType 100 private int mCallType = QnsConstants.CALL_TYPE_IDLE; 101 @Annotation.NetCapability 102 private int mNetCapability = QnsConstants.INVALID_VALUE; 103 private QnsRegistrantList mLowMediaQualityListeners = new QnsRegistrantList(); 104 private int mAccessNetwork = AccessNetworkConstants.AccessNetworkType.UNKNOWN; 105 private int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID; 106 private SparseArray<CallQuality> mCallQualities = new SparseArray(); 107 private TransportQuality mCurrentQuality; 108 /** A list of TransportQuality for each Transport type */ 109 private SparseArray<List<TransportQuality>> mTransportQualityArray = new SparseArray<>(); 110 private boolean mWwanAvailable = false; 111 private boolean mWlanAvailable = false; 112 113 private boolean mMediaThresholdBreached = false; 114 private HandlerThread mHandlerThread; 115 private ActiveCallTrackerHandler mActiveCallHandler; 116 private MediaLowQualityHandler mLowQualityHandler; 117 private String mLogTag; 118 119 private class ActiveCallTrackerHandler extends Handler { ActiveCallTrackerHandler(Looper l)120 ActiveCallTrackerHandler(Looper l) { 121 super(l); 122 } 123 124 @Override handleMessage(Message message)125 public void handleMessage(Message message) { 126 QnsAsyncResult ar; 127 int transportType; 128 Log.d(mLogTag, "handleMessage : " + message.what); 129 switch (message.what) { 130 case EVENT_DATA_CONNECTION_STATUS_CHANGED: 131 ar = (QnsAsyncResult) message.obj; 132 onDataConnectionStatusChanged( 133 (PreciseDataConnectionState) ar.mResult); 134 break; 135 136 default: 137 Log.d(mLogTag, "unHandleMessage : " + message.what); 138 break; 139 } 140 141 } 142 } 143 144 /** Tracking low quality status */ 145 private class MediaLowQualityHandler extends Handler { 146 private static final int EVENT_MEDIA_QUALITY_CHANGED = 3401; 147 private static final int EVENT_PACKET_LOSS_TIMER_EXPIRED = 3402; 148 private static final int EVENT_HYSTERESIS_FOR_NORMAL_QUALITY = 3403; 149 private static final int EVENT_POLLING_CHECK_LOW_QUALITY = 3404; 150 151 private static final int STATE_NORMAL_QUALITY = 0; 152 private static final int STATE_SUSPECT_LOW_QUALITY = 1; 153 private static final int STATE_LOW_QUALITY = 2; 154 155 private static final int HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS = 3000; 156 private static final int LOW_QUALITY_CHECK_INTERVAL_MILLIS = 15000; 157 private static final int LOW_QUALITY_CHECK_AFTER_HO_MILLIS = 3000; 158 private static final int LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE = -1; 159 160 private int mState = STATE_NORMAL_QUALITY; 161 private int mPacketLossTimerId = INVALID_ID; 162 private int mHysteresisTimerId = INVALID_ID; 163 private int mPollingCheckTimerId = INVALID_ID; 164 private MediaQualityStatus mMediaQualityStatus; 165 private String mTag; 166 MediaLowQualityHandler(Looper l)167 MediaLowQualityHandler(Looper l) { 168 super(l); 169 mTag = mLogTag + "_LQH"; 170 } 171 172 @Override handleMessage(Message message)173 public void handleMessage(Message message) { 174 Log.d(mTag, "handleMessage : " + message.what); 175 switch (message.what) { 176 case EVENT_MEDIA_QUALITY_CHANGED: 177 MediaQualityStatus status = (MediaQualityStatus) message.obj; 178 onMediaQualityChanged(status); 179 break; 180 181 case EVENT_PACKET_LOSS_TIMER_EXPIRED: 182 onPacketLossTimerExpired(message.arg1); 183 break; 184 185 case EVENT_HYSTERESIS_FOR_NORMAL_QUALITY: 186 exitLowQualityState(); 187 break; 188 189 case EVENT_POLLING_CHECK_LOW_QUALITY: 190 checkLowQuality(); 191 break; 192 193 default: 194 Log.d(mLogTag, "unHandleMessage : " + message.what); 195 break; 196 } 197 } 198 onMediaQualityChanged(MediaQualityStatus status)199 private void onMediaQualityChanged(MediaQualityStatus status) { 200 Log.d(mTag, "onMediaQualityChanged " + status); 201 int reason = thresholdBreached(status); 202 boolean needNotify = false; 203 if (reason == 0) { 204 // Threshold not breached. 205 mMediaQualityStatus = status; 206 if (mState == STATE_NORMAL_QUALITY) { 207 Log.d(mTag, "keeps normal quality."); 208 mMediaQualityStatus = status; 209 return; 210 } else { 211 // check normal quality is stable or not. 212 mHysteresisTimerId = mQnsTimer.registerTimer( 213 Message.obtain(this, EVENT_HYSTERESIS_FOR_NORMAL_QUALITY), 214 HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS); 215 } 216 } else { 217 // Threshold breached. 218 mQnsTimer.unregisterTimer(mHysteresisTimerId); 219 mHysteresisTimerId = INVALID_ID; 220 switch (mState) { 221 case STATE_NORMAL_QUALITY: 222 case STATE_SUSPECT_LOW_QUALITY: 223 if (reason == (1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS)) { 224 int delayMillis = (mConfigManager.getRTPMetricsData()).mPktLossTime; 225 if (delayMillis > 0) { 226 if (mState == STATE_NORMAL_QUALITY) { 227 enterSuspectLowQualityState(delayMillis); 228 } 229 } else if (delayMillis == 0) { 230 needNotify = true; 231 } 232 } else { 233 mQnsTimer.unregisterTimer(mPacketLossTimerId); 234 mPacketLossTimerId = INVALID_ID; 235 enterLowQualityState(status); 236 needNotify = true; 237 } 238 break; 239 240 case STATE_LOW_QUALITY: 241 if (mMediaQualityStatus.getTransportType() == status.getTransportType() 242 && thresholdBreached(mMediaQualityStatus) 243 != thresholdBreached(status)) { 244 needNotify = true; 245 } 246 break; 247 } 248 mMediaQualityStatus = status; 249 } 250 if (needNotify) { 251 enterLowQualityState(status); 252 notifyLowMediaQuality(reason); 253 } 254 255 } 256 257 @VisibleForTesting enterLowQualityState(MediaQualityStatus status)258 void enterLowQualityState(MediaQualityStatus status) { 259 Log.d(mTag, "enterLowQualityState " + status); 260 mState = STATE_LOW_QUALITY; 261 mPollingCheckTimerId = mQnsTimer.registerTimer( 262 Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY), 263 LOW_QUALITY_CHECK_INTERVAL_MILLIS); 264 } 265 enterSuspectLowQualityState(int delayMillis)266 void enterSuspectLowQualityState(int delayMillis) { 267 Log.d(mTag, "enterSuspectLowQualityState."); 268 mQnsTimer.unregisterTimer(mPacketLossTimerId); 269 Log.d(mTag, "Packet loss timer start. " + delayMillis); 270 Message msg = this.obtainMessage( 271 EVENT_PACKET_LOSS_TIMER_EXPIRED, mTransportType, 0); 272 mPacketLossTimerId = mQnsTimer.registerTimer(msg, delayMillis); 273 mState = STATE_SUSPECT_LOW_QUALITY; 274 } 275 exitLowQualityState()276 void exitLowQualityState() { 277 mState = STATE_NORMAL_QUALITY; 278 this.removeCallbacksAndMessages(null); 279 mQnsTimer.unregisterTimer(mPacketLossTimerId); 280 mQnsTimer.unregisterTimer(mHysteresisTimerId); 281 mQnsTimer.unregisterTimer(mPollingCheckTimerId); 282 mPacketLossTimerId = INVALID_ID; 283 mHysteresisTimerId = INVALID_ID; 284 mPollingCheckTimerId = INVALID_ID; 285 notifyLowMediaQuality(0); 286 } 287 checkLowQuality()288 void checkLowQuality() { 289 if (mState == STATE_NORMAL_QUALITY) { 290 Log.w(mTag, "checkLowQuality on unexpected state(normal state)."); 291 } else { 292 Log.d(mTag, "checkLowQuality"); 293 int reason = thresholdBreached(mMediaQualityStatus); 294 if (reason > 0) { 295 notifyLowMediaQuality(thresholdBreached(mMediaQualityStatus)); 296 } else if (mHysteresisTimerId != INVALID_ID) { 297 // hysteresis time to be normal state is running. let's check after that. 298 mPollingCheckTimerId = mQnsTimer.registerTimer( 299 Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY), 300 HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS); 301 } else { 302 Log.w(mTag, "Unexpected case."); 303 } 304 } 305 } 306 updateForHandover(int transportType)307 void updateForHandover(int transportType) { 308 // restart timers that they need to be restarted on new transport type. 309 if (mState == STATE_SUSPECT_LOW_QUALITY) { 310 mQnsTimer.unregisterTimer(mPacketLossTimerId); 311 Message msg = this.obtainMessage( 312 EVENT_PACKET_LOSS_TIMER_EXPIRED, transportType, 0); 313 mPacketLossTimerId = mQnsTimer.registerTimer(msg, 314 (mConfigManager.getRTPMetricsData()).mPktLossTime); 315 } 316 if (mHysteresisTimerId != INVALID_ID) { 317 mQnsTimer.unregisterTimer(mHysteresisTimerId); 318 mHysteresisTimerId = mQnsTimer.registerTimer( 319 Message.obtain(this, EVENT_HYSTERESIS_FOR_NORMAL_QUALITY), 320 HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS); 321 } 322 if (mState == STATE_LOW_QUALITY) { 323 mQnsTimer.unregisterTimer(mPollingCheckTimerId); 324 mPollingCheckTimerId = mQnsTimer.registerTimer( 325 Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY), 326 LOW_QUALITY_CHECK_AFTER_HO_MILLIS); 327 } 328 } 329 onPacketLossTimerExpired(int transportType)330 private void onPacketLossTimerExpired(int transportType) { 331 if (mTransportType != transportType) { 332 Log.d(mTag, "onPacketLossTimerExpired transport type mismatched."); 333 if (mState == STATE_SUSPECT_LOW_QUALITY) { 334 mState = STATE_NORMAL_QUALITY; 335 } 336 return; 337 } 338 if (thresholdBreached(mMediaQualityStatus) 339 == (1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS)) { 340 enterLowQualityState(mMediaQualityStatus); 341 notifyLowMediaQuality(1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS); 342 } 343 } 344 notifyLowMediaQuality(int reason)345 private void notifyLowMediaQuality(int reason) { 346 long now = QnsUtils.getSystemElapsedRealTime(); 347 TransportQuality tq = getLastTransportQuality(mTransportType); 348 if (tq != null) { 349 if (reason > 0) { 350 tq.mLowRtpQualityReportedTime = now; 351 } else { 352 tq.mLowRtpQualityReportedTime = LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE; 353 } 354 } 355 Log.d(mTag, "notifyLowMediaQuality reason:" + reason + " transport type:" 356 + QnsConstants.transportTypeToString(mTransportType)); 357 mLowMediaQualityListeners.notifyResult(reason); 358 } 359 } 360 361 class TransportQuality { 362 int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID; 363 long mLowRtpQualityReportedTime = 364 MediaLowQualityHandler.LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE; 365 List<CallQualityBlock> mCallQualityBlockList; 366 TransportQuality(int transportType)367 TransportQuality(int transportType) { 368 mTransportType = transportType; 369 mCallQualityBlockList = new ArrayList<>(); 370 } 371 isLowRtpQualityReported()372 boolean isLowRtpQualityReported() { 373 return mLowRtpQualityReportedTime 374 != MediaLowQualityHandler.LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE; 375 } 376 getLastCallQualityBlock()377 CallQualityBlock getLastCallQualityBlock() { 378 int length = mCallQualityBlockList.size(); 379 if (length > 0) { 380 return mCallQualityBlockList.get(length - 1); 381 } else { 382 return null; 383 } 384 } 385 } 386 ActiveCallTracker(int slotIndex, Looper looper)387 ActiveCallTracker(int slotIndex, Looper looper) { 388 mLogTag = ActiveCallTracker.class.getSimpleName() + "_" + slotIndex; 389 if (looper == null) { 390 mHandlerThread = new HandlerThread(ActiveCallTracker.class.getSimpleName()); 391 mHandlerThread.start(); 392 mActiveCallHandler = new ActiveCallTrackerHandler(mHandlerThread.getLooper()); 393 mLowQualityHandler = new MediaLowQualityHandler(mHandlerThread.getLooper()); 394 } else { 395 mActiveCallHandler = new ActiveCallTrackerHandler(looper); 396 mLowQualityHandler = new MediaLowQualityHandler(looper); 397 } 398 mTelephonyListener.addMediaQualityStatusCallback(mMediaQualityStatusConsumer); 399 mTransportQualityArray.put( 400 AccessNetworkConstants.TRANSPORT_TYPE_WLAN, new ArrayList<>()); 401 mTransportQualityArray.put( 402 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, new ArrayList<>()); 403 } 404 close()405 void close() { 406 mTelephonyListener.removeMediaQualityStatusCallback(mMediaQualityStatusConsumer); 407 if (mNetCapability != QnsConstants.INVALID_VALUE) { 408 mTelephonyListener.unregisterPreciseDataConnectionStateChanged( 409 mNetCapability, mActiveCallHandler); 410 mNetCapability = QnsConstants.INVALID_VALUE; 411 } 412 if (mHandlerThread != null) { 413 mHandlerThread.quitSafely(); 414 } 415 } 416 417 @VisibleForTesting onDataConnectionStatusChanged(PreciseDataConnectionState state)418 void onDataConnectionStatusChanged(PreciseDataConnectionState state) { 419 if (state == null) { 420 Log.d(mLogTag, "onDataConnectionStatusChanged with null info"); 421 return; 422 } 423 if (state.getState() == TelephonyManager.DATA_CONNECTED) { 424 int transportType = state.getTransportType(); 425 if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) { 426 Log.w(mLogTag, "Unexpected transport type on connected DataNetwork."); 427 return; 428 } 429 if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) { 430 Log.d(mLogTag, "Call started with " 431 + QnsConstants.transportTypeToString(transportType)); 432 mTransportType = transportType; 433 startTrackingTransportQuality(transportType); 434 } else if (mTransportType != transportType) { 435 Log.d(mLogTag, "Call Handed over to " 436 + QnsConstants.transportTypeToString(transportType)); 437 mTransportType = transportType; 438 onHandoverCompleted(transportType); 439 } 440 } 441 } 442 onHandoverCompleted( @ccessNetworkConstants.TransportType int dstTransportType)443 private void onHandoverCompleted( 444 @AccessNetworkConstants.TransportType int dstTransportType) { 445 long now = QnsUtils.getSystemElapsedRealTime(); 446 // complete to update TransportQuality for prev transport type 447 CallQualityBlock last = null; 448 int prevTransportType = QnsUtils.getOtherTransportType(dstTransportType); 449 TransportQuality prev = getLastTransportQuality(prevTransportType); 450 if (prev != null) { 451 last = prev.getLastCallQualityBlock(); 452 } 453 // add a new TransportQuality for new transport type 454 mTransportQualityArray.get(dstTransportType) 455 .add(new TransportQuality(dstTransportType)); 456 TransportQuality current = getLastTransportQuality(dstTransportType); 457 if (last != null) { 458 last.mDurationMillis = now - last.mCreatedElapsedTime; 459 current.mCallQualityBlockList 460 .add(new CallQualityBlock(last.mUpLinkLevel, last.mDownLinkLevel, now)); 461 } 462 mLowQualityHandler.updateForHandover(dstTransportType); 463 } 464 startTrackingTransportQuality(int transportType)465 private void startTrackingTransportQuality(int transportType) { 466 mTransportQualityArray.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).clear(); 467 mTransportQualityArray.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).clear(); 468 mTransportQualityArray.get(transportType) 469 .add(new TransportQuality(transportType)); 470 } 471 callStarted(@nsConstants.QnsCallType int callType, int netCapability)472 void callStarted(@QnsConstants.QnsCallType int callType, int netCapability) { 473 if (mCallType != QnsConstants.CALL_TYPE_IDLE) { 474 if (mCallType != callType) { 475 callTypeUpdated(callType); 476 } else { 477 Log.w(mLogTag, "call type:" + callType + " already started."); 478 } 479 } 480 Log.d(mLogTag, "callStarted callType: " + callType + " netCapa:" 481 + QnsUtils.getNameOfNetCapability(netCapability)); 482 mCallType = callType; 483 mNetCapability = netCapability; 484 //Transport type will be updated when EVENT_DATA_CONNECTION_STATUS_CHANGED occurs. 485 PreciseDataConnectionState dataState = 486 mTelephonyListener.getLastPreciseDataConnectionState(netCapability); 487 if (dataState != null && dataState.getTransportType() 488 != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) { 489 mTransportType = dataState.getTransportType(); 490 startTrackingTransportQuality(mTransportType); 491 } 492 mTelephonyListener.registerPreciseDataConnectionStateChanged(mNetCapability, 493 mActiveCallHandler, EVENT_DATA_CONNECTION_STATUS_CHANGED, null, true); 494 } 495 callTypeUpdated(@nsConstants.QnsCallType int callType)496 private void callTypeUpdated(@QnsConstants.QnsCallType int callType) { 497 Log.d(mLogTag, "callTypeUpdated from " + mCallType + " to " + callType); 498 mCallType = callType; 499 } 500 callEnded()501 void callEnded() { 502 mLowQualityHandler.exitLowQualityState(); 503 long now = QnsUtils.getSystemElapsedRealTime(); 504 // complete to update TransportQuality for prev transport type 505 CallQualityBlock last = null; 506 TransportQuality prev = getLastTransportQuality(mTransportType); 507 if (prev != null) { 508 last = prev.getLastCallQualityBlock(); 509 } 510 if (last != null) { 511 last.mDurationMillis = now - last.mCreatedElapsedTime; 512 } 513 long upLinkQualityOverWwan = mActiveCallTracker 514 .getUpLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 515 long upLinkQualityOverWlan = mActiveCallTracker 516 .getUpLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WLAN); 517 long downLinkQualityOverWwan = mActiveCallTracker 518 .getDownLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 519 long downLinkQualityOverWlan = mActiveCallTracker 520 .getDownLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WLAN); 521 StringBuilder sb = new StringBuilder(); 522 sb.append("CallQuality [WWAN:"); 523 if (upLinkQualityOverWwan == QnsConstants.INVALID_VALUE 524 || downLinkQualityOverWwan == QnsConstants.INVALID_VALUE) { 525 sb.append("Not available] "); 526 } else { 527 sb.append("upLinkQualityOverWwan = ").append(upLinkQualityOverWwan) 528 .append(", downLinkQualityOverWwan = ").append(downLinkQualityOverWwan) 529 .append("] "); 530 } 531 sb.append("[WLAN:"); 532 if (upLinkQualityOverWlan == QnsConstants.INVALID_VALUE 533 || downLinkQualityOverWlan == QnsConstants.INVALID_VALUE) { 534 sb.append("Not available] "); 535 } else { 536 sb.append("upLinkQualityOverWlan = ").append(upLinkQualityOverWwan) 537 .append(", downLinkQualityOverWlan = ").append(downLinkQualityOverWwan) 538 .append("] "); 539 } 540 Log.d(mLogTag, "callEnded callType: " + mCallType + " netCapa:" 541 + QnsUtils.getNameOfNetCapability(mNetCapability) + " " + sb.toString()); 542 mCallType = QnsConstants.CALL_TYPE_IDLE; 543 mTelephonyListener.unregisterPreciseDataConnectionStateChanged( 544 mNetCapability, mActiveCallHandler); 545 mNetCapability = QnsConstants.INVALID_VALUE; 546 mAccessNetwork = AccessNetworkConstants.AccessNetworkType.UNKNOWN; 547 mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID; 548 } 549 onMediaQualityStatusChanged(MediaQualityStatus status)550 void onMediaQualityStatusChanged(MediaQualityStatus status) { 551 if (status == null) { 552 Log.e(mLogTag, "null MediaQualityStatus received."); 553 return; 554 } 555 Message msg = mLowQualityHandler 556 .obtainMessage(MediaLowQualityHandler.EVENT_MEDIA_QUALITY_CHANGED, status); 557 mLowQualityHandler.sendMessage(msg); 558 } 559 getTransportType()560 int getTransportType() { 561 return this.mTransportType; 562 } 563 getCallType()564 int getCallType() { 565 return this.mCallType; 566 } 567 getNetCapability()568 int getNetCapability() { 569 return this.mNetCapability; 570 } 571 572 @VisibleForTesting getLastTransportQuality(int transportType)573 TransportQuality getLastTransportQuality(int transportType) { 574 if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) { 575 Log.w(mLogTag, "getLastTransportQuality with invalid transport type."); 576 return null; 577 } 578 int size = mTransportQualityArray.get(transportType).size(); 579 if (size > 0) { 580 return mTransportQualityArray.get(transportType).get(size - 1); 581 } else { 582 return null; 583 } 584 } 585 586 @VisibleForTesting getTransportQualityList(int transportType)587 List<TransportQuality> getTransportQualityList(int transportType) { 588 return mTransportQualityArray.get(transportType); 589 } 590 getUpLinkQualityLevelDuringCall(int transportType)591 long getUpLinkQualityLevelDuringCall(int transportType) { 592 List<TransportQuality> tqList = getTransportQualityList(transportType); 593 long sumUplinkQualityLevelVolume = 0; 594 long totalDuration = 0; 595 for (int i = 0; i < tqList.size(); i++) { 596 List<CallQualityBlock> callQualityBlockList = tqList.get(i).mCallQualityBlockList; 597 for (int j = 0; j < callQualityBlockList.size(); j++) { 598 CallQualityBlock cq = callQualityBlockList.get(j); 599 sumUplinkQualityLevelVolume += cq.getUpLinkQualityVolume(); 600 long durationMillis = cq.mDurationMillis; 601 if (i == tqList.size() - 1 && j == callQualityBlockList.size() - 1) { 602 if (durationMillis == 0) { 603 durationMillis = QnsUtils.getSystemElapsedRealTime() 604 - cq.mCreatedElapsedTime; 605 } 606 } 607 if (durationMillis > 0) { 608 totalDuration += durationMillis; 609 } else { 610 return -1; 611 } 612 } 613 } 614 if (totalDuration <= 0) { 615 return QnsConstants.INVALID_VALUE; 616 } 617 long qualityLevel = sumUplinkQualityLevelVolume / totalDuration; 618 Log.d(mLogTag, "getUplinkQualityLevel for [" + QnsConstants 619 .transportTypeToString(transportType) + "] totalQualityVolume: " 620 + sumUplinkQualityLevelVolume + ", totalDuration: " + totalDuration 621 + " level:" + qualityLevel); 622 return qualityLevel; 623 } 624 getDownLinkQualityLevelDuringCall(int transportType)625 long getDownLinkQualityLevelDuringCall(int transportType) { 626 List<TransportQuality> tqList = getTransportQualityList(transportType); 627 long sumDownLinkQualityLevelVolume = 0; 628 long totalDuration = 0; 629 for (int i = 0; i < tqList.size(); i++) { 630 List<CallQualityBlock> callQualityBlockList = tqList.get(i).mCallQualityBlockList; 631 for (int j = 0; j < callQualityBlockList.size(); j++) { 632 CallQualityBlock cq = callQualityBlockList.get(j); 633 sumDownLinkQualityLevelVolume += cq.getDownLinkQualityVolume(); 634 long durationMillis = cq.mDurationMillis; 635 if (i == tqList.size() - 1 && j == callQualityBlockList.size() - 1) { 636 if (durationMillis == 0) { 637 durationMillis = QnsUtils.getSystemElapsedRealTime() 638 - cq.mCreatedElapsedTime; 639 } 640 } 641 if (durationMillis > 0) { 642 totalDuration += durationMillis; 643 } else { 644 return QnsConstants.INVALID_VALUE; 645 } 646 } 647 } 648 if (totalDuration <= 0) { 649 return QnsConstants.INVALID_VALUE; 650 } 651 long qualityLevel = sumDownLinkQualityLevelVolume / totalDuration; 652 Log.d(mLogTag, "getDownLinkQualityLevel for [" + QnsConstants 653 .transportTypeToString(transportType) + "] totalQualityVolume: " 654 + sumDownLinkQualityLevelVolume + ", totalDuration: " + totalDuration 655 + " level:" + qualityLevel); 656 return qualityLevel; 657 } 658 updateCallQuality(CallState state)659 void updateCallQuality(CallState state) { 660 if (state == null) { 661 Log.w(mLogTag, "updateCallQuality Null CallState."); 662 return; 663 } 664 CallQuality cq = state.getCallQuality(); 665 if (cq == null || isDummyCallQuality(cq)) { 666 return; 667 } 668 mActiveCallHandler.post(() -> onUpdateCallQuality(cq)); 669 } 670 onUpdateCallQuality(CallQuality cq)671 private void onUpdateCallQuality(CallQuality cq) { 672 TransportQuality transportQuality = getLastTransportQuality(mTransportType); 673 if (transportQuality != null) { 674 long now = QnsUtils.getSystemElapsedRealTime(); 675 CallQualityBlock prev = transportQuality.getLastCallQualityBlock(); 676 if (prev != null) { 677 prev.mDurationMillis = now - prev.mCreatedElapsedTime; 678 } 679 transportQuality.mCallQualityBlockList.add( 680 new CallQualityBlock( 681 cq.getUplinkCallQualityLevel(), cq.getDownlinkCallQualityLevel(), 682 now)); 683 } 684 } 685 isDummyCallQuality(CallQuality cq)686 private boolean isDummyCallQuality(CallQuality cq) { 687 return (cq.getNumRtpPacketsTransmitted() == 0 688 && cq.getNumRtpPacketsReceived() == 0 689 && cq.getUplinkCallQualityLevel() == 0 690 && cq.getDownlinkCallQualityLevel() == 0); 691 } 692 /** 693 * Register an event for low media quality report. 694 * 695 * @param h the Handler to get event. 696 * @param what the event. 697 * @param userObj user object. 698 */ registerLowMediaQualityListener( Handler h, int what, Object userObj)699 void registerLowMediaQualityListener( 700 Handler h, int what, Object userObj) { 701 Log.d(mLogTag, "registerLowMediaQualityListener"); 702 if (h != null) { 703 QnsRegistrant r = new QnsRegistrant(h, what, userObj); 704 mLowMediaQualityListeners.add(r); 705 } 706 } 707 708 /** 709 * Unregister an event for low media quality report. 710 * 711 * @param h the handler to get event. 712 */ unregisterLowMediaQualityListener(Handler h)713 void unregisterLowMediaQualityListener(Handler h) { 714 if (h != null) { 715 mLowMediaQualityListeners.remove(h); 716 } 717 } 718 719 @VisibleForTesting thresholdBreached(MediaQualityStatus status)720 int thresholdBreached(MediaQualityStatus status) { 721 int breachedReason = 0; 722 QnsCarrierConfigManager.RtpMetricsConfig rtpConfig = mConfigManager.getRTPMetricsData(); 723 if (status.getRtpPacketLossRate() > 0 724 && status.getRtpPacketLossRate() >= rtpConfig.mPktLossRate) { 725 breachedReason |= 1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS; 726 } 727 if (status.getRtpJitterMillis() > 0 728 && status.getRtpJitterMillis() >= rtpConfig.mJitter) { 729 breachedReason |= 1 << QnsConstants.RTP_LOW_QUALITY_REASON_JITTER; 730 } 731 if (status.getRtpInactivityMillis() > 0 732 && status.getRtpInactivityMillis() >= rtpConfig.mNoRtpInterval) { 733 breachedReason |= 1 << QnsConstants.RTP_LOW_QUALITY_REASON_NO_RTP; 734 } 735 return breachedReason; 736 } 737 worseThanBefore(MediaQualityStatus before, MediaQualityStatus now)738 boolean worseThanBefore(MediaQualityStatus before, MediaQualityStatus now) { 739 return thresholdBreached(now) > thresholdBreached(before); 740 } 741 } 742 QnsCallStatusTracker(QnsTelephonyListener telephonyListener, QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex)743 QnsCallStatusTracker(QnsTelephonyListener telephonyListener, 744 QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex) { 745 this(telephonyListener, configManager, qnsTimer, slotIndex, null); 746 } 747 748 /** Only for test */ 749 @VisibleForTesting QnsCallStatusTracker(QnsTelephonyListener telephonyListener, QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex, Looper looper)750 QnsCallStatusTracker(QnsTelephonyListener telephonyListener, 751 QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex, 752 Looper looper) { 753 mLogTag = QnsCallStatusTracker.class.getSimpleName() + "_" + slotIndex; 754 mTelephonyListener = telephonyListener; 755 mConfigManager = configManager; 756 mQnsTimer = qnsTimer; 757 mActiveCallTracker = new ActiveCallTracker(slotIndex, looper); 758 mTelephonyListener.addCallStatesChangedCallback(mCallStatesConsumer); 759 mTelephonyListener.addSrvccStateChangedCallback(mSrvccStateConsumer); 760 } 761 close()762 void close() { 763 mTelephonyListener.removeCallStatesChangedCallback(mCallStatesConsumer); 764 mTelephonyListener.removeSrvccStateChangedCallback(mSrvccStateConsumer); 765 if (mActiveCallTracker != null) { 766 mActiveCallTracker.close(); 767 } 768 } 769 updateCallState(List<CallState> callStateList)770 void updateCallState(List<CallState> callStateList) { 771 List<CallState> imsCallStateList = new ArrayList<>(); 772 StringBuilder sb = new StringBuilder(""); 773 774 if (callStateList.size() > 0) { 775 for (CallState cs : callStateList) { 776 if (cs.getImsCallServiceType() != ImsCallProfile.SERVICE_TYPE_NONE 777 || cs.getImsCallType() != ImsCallProfile.CALL_TYPE_NONE) { 778 if (cs.getCallState() != PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED) { 779 imsCallStateList.add(cs); 780 sb.append("{" + cs + "}"); 781 } 782 } 783 } 784 } 785 int ongoingCallNum = imsCallStateList.size(); 786 mCallStates = imsCallStateList; 787 Log.d(mLogTag, "updateCallState callNum:(" + ongoingCallNum + "): [" + sb + "]"); 788 if (imsCallStateList.size() == 0) { 789 if (mLastNormalCallType != QnsConstants.CALL_TYPE_IDLE) { 790 mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE; 791 notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType); 792 } 793 if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE) { 794 mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE; 795 if (mEmergencyOverIms) { 796 mEmergencyOverIms = false; 797 notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastEmergencyCallType); 798 } else { 799 notifyCallType(NetworkCapabilities.NET_CAPABILITY_EIMS, mLastEmergencyCallType); 800 } 801 } 802 } else { 803 //1. Notify Call Type IDLE, if the call was removed from the call list. 804 if (mLastNormalCallType != QnsConstants.CALL_TYPE_IDLE 805 && !hasVideoCall() && !hasVoiceCall()) { 806 mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE; 807 notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType); 808 809 } 810 if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE && !hasEmergencyCall()) { 811 mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE; 812 if (mEmergencyOverIms) { 813 mEmergencyOverIms = false; 814 notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastEmergencyCallType); 815 } else { 816 notifyCallType(NetworkCapabilities.NET_CAPABILITY_EIMS, mLastEmergencyCallType); 817 } 818 } 819 //2. Notify a new ongoing call type 820 if (hasEmergencyCall()) { 821 if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_EMERGENCY) { 822 mLastEmergencyCallType = QnsConstants.CALL_TYPE_EMERGENCY; 823 if (!isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_EIMS) 824 && isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_IMS)) { 825 notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, 826 mLastEmergencyCallType); 827 mEmergencyOverIms = true; 828 } else { 829 notifyCallType(NetworkCapabilities.NET_CAPABILITY_EIMS, 830 mLastEmergencyCallType); 831 } 832 } 833 } else if (hasVideoCall()) { 834 if (mLastNormalCallType != QnsConstants.CALL_TYPE_VIDEO) { 835 mLastNormalCallType = QnsConstants.CALL_TYPE_VIDEO; 836 notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType); 837 } 838 } else if (hasVoiceCall()) { 839 if (mLastNormalCallType != QnsConstants.CALL_TYPE_VOICE) { 840 mLastNormalCallType = QnsConstants.CALL_TYPE_VOICE; 841 notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType); 842 } 843 } 844 if (mActiveCallTracker.getCallType() != QnsConstants.CALL_TYPE_IDLE) { 845 mActiveCallTracker.updateCallQuality(getActiveCall()); 846 } 847 } 848 } 849 notifyCallType(int netCapability, int callType)850 private void notifyCallType(int netCapability, int callType) { 851 Log.d(mLogTag, "notifyCallType for " + QnsUtils.getNameOfNetCapability(netCapability) 852 + ", callType:" + callType); 853 if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS 854 && mCallTypeChangedEventListener != null) { 855 mCallTypeChangedEventListener.notifyResult(callType); 856 } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS 857 && mEmergencyCallTypeChangedEventListener != null) { 858 mEmergencyCallTypeChangedEventListener.notifyResult(callType); 859 } 860 if (callType == QnsConstants.CALL_TYPE_IDLE) { 861 mActiveCallTracker.callEnded(); 862 } else { 863 mActiveCallTracker.callStarted(callType, netCapability); 864 } 865 mQnsTimer.updateCallState(callType); 866 } 867 isCallIdle()868 boolean isCallIdle() { 869 return mCallStates.size() == 0; 870 } 871 isCallIdle(int netCapability)872 boolean isCallIdle(int netCapability) { 873 int callNum = mCallStates.size(); 874 if (callNum == 0) { 875 return true; 876 } 877 if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) { 878 return (mLastNormalCallType == QnsConstants.CALL_TYPE_IDLE) 879 && (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE 880 && !mEmergencyOverIms); 881 } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) { 882 return mLastEmergencyCallType == QnsConstants.CALL_TYPE_IDLE || mEmergencyOverIms; 883 } 884 return false; 885 } 886 hasEmergencyCall()887 boolean hasEmergencyCall() { 888 for (CallState cs : mCallStates) { 889 if (cs.getImsCallServiceType() == ImsCallProfile.SERVICE_TYPE_EMERGENCY 890 && cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) { 891 return true; 892 } 893 } 894 return false; 895 } 896 getActiveCall()897 CallState getActiveCall() { 898 for (CallState cs : mCallStates) { 899 if (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) { 900 return cs; 901 } 902 } 903 return null; 904 } 905 hasVideoCall()906 boolean hasVideoCall() { 907 for (CallState cs : mCallStates) { 908 if (cs.getImsCallServiceType() == ImsCallProfile.SERVICE_TYPE_NORMAL 909 && cs.getImsCallType() == ImsCallProfile.CALL_TYPE_VT 910 && (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING 911 || cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_HOLDING 912 || cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE)) { 913 return true; 914 } 915 } 916 return false; 917 } 918 hasVoiceCall()919 boolean hasVoiceCall() { 920 for (CallState cs : mCallStates) { 921 if (cs.getImsCallServiceType() == ImsCallProfile.SERVICE_TYPE_NORMAL 922 && cs.getImsCallType() == ImsCallProfile.CALL_TYPE_VOICE) { 923 return true; 924 } 925 } 926 return false; 927 } 928 929 /** 930 * register call type changed event. 931 * 932 * @param netCapability Network Capability of caller 933 * @param h Handler want to receive event. 934 * @param what event Id to receive 935 * @param userObj user object 936 */ registerCallTypeChangedListener( int netCapability, @NonNull Handler h, int what, Object userObj)937 void registerCallTypeChangedListener( 938 int netCapability, @NonNull Handler h, int what, Object userObj) { 939 if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS 940 && netCapability != NetworkCapabilities.NET_CAPABILITY_EIMS) { 941 Log.d(mLogTag, "registerCallTypeChangedListener : wrong netCapability"); 942 return; 943 } 944 if (h != null) { 945 QnsRegistrant r = new QnsRegistrant(h, what, userObj); 946 if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) { 947 mCallTypeChangedEventListener = r; 948 } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) { 949 mEmergencyCallTypeChangedEventListener = r; 950 } 951 } else { 952 Log.d(mLogTag, "registerCallTypeChangedListener : Handler is Null"); 953 } 954 } 955 956 /** 957 * Unregister call type changed event. 958 * 959 * @param netCapability Network Capability of caller 960 * @param h Handler want to receive event. 961 */ unregisterCallTypeChangedListener(int netCapability, @NonNull Handler h)962 void unregisterCallTypeChangedListener(int netCapability, @NonNull Handler h) { 963 if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS 964 && netCapability != NetworkCapabilities.NET_CAPABILITY_EIMS) { 965 Log.d(mLogTag, "unregisterCallTypeChangedListener : wrong netCapability"); 966 return; 967 } 968 if (h != null) { 969 if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) { 970 mCallTypeChangedEventListener = null; 971 } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) { 972 mEmergencyCallTypeChangedEventListener = null; 973 } 974 } else { 975 Log.d(mLogTag, "unregisterCallTypeChangedListener : Handler is Null"); 976 } 977 } 978 getActiveCallTracker()979 ActiveCallTracker getActiveCallTracker() { 980 return mActiveCallTracker; 981 } 982 983 @VisibleForTesting onSrvccStateChangedInternal(int srvccState)984 void onSrvccStateChangedInternal(int srvccState) { 985 if (srvccState == TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED) { 986 mCallStates.clear(); 987 if (mLastNormalCallType != QnsConstants.CALL_TYPE_IDLE) { 988 mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE; 989 if (mCallTypeChangedEventListener != null) { 990 mCallTypeChangedEventListener.notifyResult(mLastNormalCallType); 991 } 992 } 993 if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE) { 994 mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE; 995 if (mEmergencyOverIms) { 996 mEmergencyOverIms = false; 997 if (mCallTypeChangedEventListener != null) { 998 mCallTypeChangedEventListener.notifyResult(mLastEmergencyCallType); 999 } 1000 } else { 1001 if (mEmergencyCallTypeChangedEventListener != null) { 1002 mEmergencyCallTypeChangedEventListener.notifyResult(mLastEmergencyCallType); 1003 } 1004 } 1005 } 1006 } 1007 } 1008 1009 isDataNetworkConnected(int netCapability)1010 private boolean isDataNetworkConnected(int netCapability) { 1011 PreciseDataConnectionState preciseDataStatus = 1012 mTelephonyListener.getLastPreciseDataConnectionState(netCapability); 1013 1014 if (preciseDataStatus == null) return false; 1015 int state = preciseDataStatus.getState(); 1016 return (state == TelephonyManager.DATA_CONNECTED 1017 || state == TelephonyManager.DATA_HANDOVER_IN_PROGRESS 1018 || state == TelephonyManager.DATA_SUSPENDED); 1019 } 1020 } 1021