summaryrefslogtreecommitdiff
path: root/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java')
-rw-r--r--services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java746
1 files changed, 735 insertions, 11 deletions
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java
index 0800787..095cf95 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java
@@ -19,12 +19,20 @@ package com.android.telephony.qns;
import android.annotation.NonNull;
import android.net.NetworkCapabilities;
import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation;
+import android.telephony.CallQuality;
import android.telephony.CallState;
import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.MediaQualityStatus;
import android.util.Log;
+import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -38,19 +46,695 @@ import java.util.function.Consumer;
public class QnsCallStatusTracker {
private final String mLogTag;
private QnsTelephonyListener mTelephonyListener;
+ private QnsCarrierConfigManager mConfigManager;
private List<CallState> mCallStates = new ArrayList<>();
private QnsRegistrant mCallTypeChangedEventListener;
private QnsRegistrant mEmergencyCallTypeChangedEventListener;
private int mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE;
private int mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE;
private boolean mEmergencyOverIms;
+ private ActiveCallTracker mActiveCallTracker;
private Consumer<List<CallState>> mCallStatesConsumer =
callStateList -> updateCallState(callStateList);
private Consumer<Integer> mSrvccStateConsumer = state -> onSrvccStateChangedInternal(state);
+ private Consumer<MediaQualityStatus> mMediaQualityStatusConsumer =
+ status -> mActiveCallTracker.onMediaQualityStatusChanged(status);
- QnsCallStatusTracker(QnsTelephonyListener telephonyListener, int slotIndex) {
+ static class CallQualityBlock {
+ int mUpLinkLevel;
+ int mDownLinkLevel;
+ long mCreatedElapsedTime;
+ long mDurationMillis;
+ CallQualityBlock(int uplinkLevel, int downLinkLevel, long createdElapsedTime) {
+ mUpLinkLevel = uplinkLevel;
+ mDownLinkLevel = downLinkLevel;
+ mCreatedElapsedTime = createdElapsedTime;
+ }
+
+ long getUpLinkQualityVolume() {
+ if (mDurationMillis > 0) {
+ return mUpLinkLevel * mDurationMillis;
+ } else {
+ long now = QnsUtils.getSystemElapsedRealTime();
+ return (now - mCreatedElapsedTime) * mUpLinkLevel;
+ }
+ }
+
+ long getDownLinkQualityVolume() {
+ if (mDurationMillis > 0) {
+ return mDownLinkLevel * mDurationMillis;
+ } else {
+ long now = QnsUtils.getSystemElapsedRealTime();
+ return (now - mCreatedElapsedTime) * mDownLinkLevel;
+ }
+ }
+ }
+
+ class ActiveCallTracker {
+ private static final int EVENT_DATA_CONNECTION_STATUS_CHANGED = 3300;
+
+ @QnsConstants.QnsCallType
+ private int mCallType = QnsConstants.CALL_TYPE_IDLE;
+ @Annotation.NetCapability
+ private int mNetCapability = QnsConstants.INVALID_VALUE;
+ private QnsRegistrantList mLowMediaQualityListeners = new QnsRegistrantList();
+ private int mAccessNetwork = AccessNetworkConstants.AccessNetworkType.UNKNOWN;
+ private int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+ private SparseArray<CallQuality> mCallQualities = new SparseArray();
+ private TransportQuality mCurrentQuality;
+ /** A list of TransportQuality for each Transport type */
+ private SparseArray<List<TransportQuality>> mTransportQualityArray = new SparseArray<>();
+ private boolean mWwanAvailable = false;
+ private boolean mWlanAvailable = false;
+
+ private boolean mMediaThresholdBreached = false;
+ private HandlerThread mHandlerThread;
+ private ActiveCallTrackerHandler mActiveCallHandler;
+ private MediaLowQualityHandler mLowQualityHandler;
+ private String mLogTag;
+
+ private class ActiveCallTrackerHandler extends Handler {
+ ActiveCallTrackerHandler(Looper l) {
+ super(l);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ QnsAsyncResult ar;
+ int transportType;
+ Log.d(mLogTag, "handleMessage : " + message.what);
+ switch (message.what) {
+ case EVENT_DATA_CONNECTION_STATUS_CHANGED:
+ ar = (QnsAsyncResult) message.obj;
+ onDataConnectionStatusChanged(
+ (PreciseDataConnectionState) ar.mResult);
+ break;
+
+ default:
+ Log.d(mLogTag, "unHandleMessage : " + message.what);
+ break;
+ }
+
+ }
+ }
+
+ /** Tracking low quality status */
+ private class MediaLowQualityHandler extends Handler {
+ private static final int EVENT_MEDIA_QUALITY_CHANGED = 3401;
+ private static final int EVENT_PACKET_LOSS_TIMER_EXPIRED = 3402;
+ private static final int EVENT_HYSTERESIS_FOR_NORMAL_QUALITY = 3403;
+ private static final int EVENT_POLLING_CHECK_LOW_QUALITY = 3404;
+ private static final int EVENT_LOW_QUALITY_HANDLER_MAX = 3405;
+
+ private static final int STATE_NORMAL_QUALITY = 0;
+ private static final int STATE_SUSPECT_LOW_QUALITY = 1;
+ private static final int STATE_LOW_QUALITY = 2;
+
+ private static final int HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS = 3000;
+ private static final int LOW_QUALITY_CHECK_INTERVAL_MILLIS = 15000;
+ private static final int LOW_QUALITY_CHECK_AFTER_HO_MILLIS = 3000;
+ private static final int LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE = -1;
+
+ private int mState = STATE_NORMAL_QUALITY;
+ private MediaQualityStatus mMediaQualityStatus;
+ private String mTag;
+
+ MediaLowQualityHandler(Looper l) {
+ super(l);
+ mTag = mLogTag + "_LQH";
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ Log.d(mTag, "handleMessage : " + message.what);
+ switch (message.what) {
+ case EVENT_MEDIA_QUALITY_CHANGED:
+ MediaQualityStatus status = (MediaQualityStatus) message.obj;
+ onMediaQualityChanged(status);
+ break;
+
+ case EVENT_PACKET_LOSS_TIMER_EXPIRED:
+ onPacketLossTimerExpired(message.arg1);
+ break;
+
+ case EVENT_HYSTERESIS_FOR_NORMAL_QUALITY:
+ exitLowQualityState();
+ break;
+
+ case EVENT_POLLING_CHECK_LOW_QUALITY:
+ checkLowQuality();
+ break;
+
+ default:
+ Log.d(mLogTag, "unHandleMessage : " + message.what);
+ break;
+ }
+ }
+
+ private void onMediaQualityChanged(MediaQualityStatus status) {
+ Log.d(mTag, "onMediaQualityChanged " + status);
+ int reason = thresholdBreached(status);
+ boolean needNotify = false;
+ if (reason == 0) {
+ // Threshold not breached.
+ mMediaQualityStatus = status;
+ if (mState == STATE_NORMAL_QUALITY) {
+ Log.d(mTag, "keeps normal quality.");
+ mMediaQualityStatus = status;
+ return;
+ } else {
+ // check normal quality is stable or not.
+ this.sendEmptyMessageDelayed(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY,
+ HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS);
+ }
+ } else {
+ // Threshold breached.
+ this.removeMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY);
+ switch (mState) {
+ case STATE_NORMAL_QUALITY:
+ case STATE_SUSPECT_LOW_QUALITY:
+ if (reason == (1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS)) {
+ int delayMillis = (mConfigManager.getRTPMetricsData()).mPktLossTime;
+ if (delayMillis > 0) {
+ if (mState == STATE_NORMAL_QUALITY) {
+ enterSuspectLowQualityState(delayMillis);
+ }
+ } else if (delayMillis == 0) {
+ needNotify = true;
+ }
+ } else {
+ removeMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED);
+ enterLowQualityState(status);
+ needNotify = true;
+ }
+ break;
+
+ case STATE_LOW_QUALITY:
+ if (mMediaQualityStatus.getTransportType() == status.getTransportType()
+ && thresholdBreached(mMediaQualityStatus)
+ != thresholdBreached(status)) {
+ needNotify = true;
+ }
+ break;
+ }
+ mMediaQualityStatus = status;
+ }
+ if (needNotify) {
+ enterLowQualityState(status);
+ notifyLowMediaQuality(reason);
+ }
+
+ }
+
+ @VisibleForTesting
+ void enterLowQualityState(MediaQualityStatus status) {
+ Log.d(mTag, "enterLowQualityState " + status);
+ mState = STATE_LOW_QUALITY;
+ this.sendEmptyMessageDelayed(
+ EVENT_POLLING_CHECK_LOW_QUALITY, LOW_QUALITY_CHECK_INTERVAL_MILLIS);
+ }
+
+ void enterSuspectLowQualityState(int delayMillis) {
+ Log.d(mTag, "enterSuspectLowQualityState.");
+ if (!this.hasMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED)) {
+ this.removeMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED);
+ }
+ Log.d(mTag, "Packet loss timer start. " + delayMillis);
+ Message msg = this.obtainMessage(
+ EVENT_PACKET_LOSS_TIMER_EXPIRED, mTransportType, 0);
+ this.sendMessageDelayed(msg, delayMillis);
+ mState = STATE_SUSPECT_LOW_QUALITY;
+ }
+
+ void exitLowQualityState() {
+ mState = STATE_NORMAL_QUALITY;
+ for (int i = EVENT_PACKET_LOSS_TIMER_EXPIRED;
+ i < EVENT_LOW_QUALITY_HANDLER_MAX; i++) {
+ this.removeMessages(i);
+ }
+ notifyLowMediaQuality(0);
+ }
+
+ void checkLowQuality() {
+ if (mState == STATE_NORMAL_QUALITY) {
+ Log.w(mTag, "checkLowQuality on unexpected state(normal state).");
+ } else {
+ Log.d(mTag, "checkLowQuality");
+ int reason = thresholdBreached(mMediaQualityStatus);
+ if (reason > 0) {
+ notifyLowMediaQuality(thresholdBreached(mMediaQualityStatus));
+ } else if (this.hasMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY)) {
+ // hysteresis time to be normal state is running. let's check after that.
+ this.sendEmptyMessageDelayed(EVENT_POLLING_CHECK_LOW_QUALITY,
+ HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS);
+ } else {
+ Log.w(mTag, "Unexpected case.");
+ }
+ }
+ }
+
+ void updateForHandover(int transportType) {
+ // restart timers that they need to be restarted on new transport type.
+ if (mState == STATE_SUSPECT_LOW_QUALITY) {
+ this.removeMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED);
+ Message msg = this.obtainMessage(
+ EVENT_PACKET_LOSS_TIMER_EXPIRED, transportType, 0);
+ this.sendMessageDelayed(msg, (mConfigManager.getRTPMetricsData()).mPktLossTime);
+ }
+ if (this.hasMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY)) {
+ this.removeMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY);
+ this.sendEmptyMessageDelayed(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY,
+ HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS);
+ }
+ if (mState == STATE_LOW_QUALITY) {
+ this.removeMessages(EVENT_POLLING_CHECK_LOW_QUALITY);
+ this.sendEmptyMessageDelayed(EVENT_POLLING_CHECK_LOW_QUALITY,
+ LOW_QUALITY_CHECK_AFTER_HO_MILLIS);
+ }
+ }
+
+ private void onPacketLossTimerExpired(int transportType) {
+ if (mTransportType != transportType) {
+ Log.d(mTag, "onPacketLossTimerExpired transport type mismatched.");
+ if (mState == STATE_SUSPECT_LOW_QUALITY) {
+ mState = STATE_NORMAL_QUALITY;
+ }
+ return;
+ }
+ if (thresholdBreached(mMediaQualityStatus)
+ == (1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS)) {
+ enterLowQualityState(mMediaQualityStatus);
+ notifyLowMediaQuality(1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS);
+ }
+ }
+
+ private void notifyLowMediaQuality(int reason) {
+ long now = QnsUtils.getSystemElapsedRealTime();
+ TransportQuality tq = getLastTransportQuality(mTransportType);
+ if (tq != null) {
+ if (reason > 0) {
+ tq.mLowRtpQualityReportedTime = now;
+ } else {
+ tq.mLowRtpQualityReportedTime = LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE;
+ }
+ }
+ Log.d(mTag, "notifyLowMediaQuality reason:" + reason + " transport type:"
+ + AccessNetworkConstants.transportTypeToString(mTransportType));
+ mLowMediaQualityListeners.notifyResult(reason);
+ }
+ }
+
+ class TransportQuality {
+ int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+ long mLowRtpQualityReportedTime =
+ MediaLowQualityHandler.LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE;
+ List<CallQualityBlock> mCallQualityBlockList;
+
+ TransportQuality(int transportType) {
+ mTransportType = transportType;
+ mCallQualityBlockList = new ArrayList<>();
+ }
+
+ boolean isLowRtpQualityReported() {
+ return mLowRtpQualityReportedTime
+ != MediaLowQualityHandler.LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE;
+ }
+
+ CallQualityBlock getLastCallQualityBlock() {
+ int length = mCallQualityBlockList.size();
+ if (length > 0) {
+ return mCallQualityBlockList.get(length - 1);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ ActiveCallTracker(int slotIndex, Looper looper) {
+ mLogTag = ActiveCallTracker.class.getSimpleName() + "_" + slotIndex;
+ if (looper == null) {
+ mHandlerThread = new HandlerThread(ActiveCallTracker.class.getSimpleName());
+ mHandlerThread.start();
+ mActiveCallHandler = new ActiveCallTrackerHandler(mHandlerThread.getLooper());
+ mLowQualityHandler = new MediaLowQualityHandler(mHandlerThread.getLooper());
+ } else {
+ mActiveCallHandler = new ActiveCallTrackerHandler(looper);
+ mLowQualityHandler = new MediaLowQualityHandler(looper);
+ }
+ mTelephonyListener.addMediaQualityStatusCallback(mMediaQualityStatusConsumer);
+ mTransportQualityArray.put(
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN, new ArrayList<>());
+ mTransportQualityArray.put(
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN, new ArrayList<>());
+ }
+
+ void close() {
+ mTelephonyListener.removeMediaQualityStatusCallback(mMediaQualityStatusConsumer);
+ mTelephonyListener.unregisterPreciseDataConnectionStateChanged(
+ mNetCapability, mActiveCallHandler);
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ }
+ }
+
+ @VisibleForTesting
+ void onDataConnectionStatusChanged(PreciseDataConnectionState state) {
+ if (state == null) {
+ Log.d(mLogTag, "onDataConnectionStatusChanged with null info");
+ return;
+ }
+ if (state.getState() == TelephonyManager.DATA_CONNECTED) {
+ int transportType = state.getTransportType();
+ if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
+ Log.w(mLogTag, "Unexpected transport type on connected DataNetwork.");
+ return;
+ }
+ if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
+ Log.d(mLogTag, "Call started with "
+ + AccessNetworkConstants.transportTypeToString(transportType));
+ mTransportType = transportType;
+ startTrackingTransportQuality(transportType);
+ } else if (mTransportType != transportType) {
+ Log.d(mLogTag, "Call Handed over to "
+ + AccessNetworkConstants.transportTypeToString(transportType));
+ mTransportType = transportType;
+ onHandoverCompleted(transportType);
+ }
+ }
+ }
+
+ private void onHandoverCompleted(
+ @AccessNetworkConstants.TransportType int dstTransportType) {
+ long now = QnsUtils.getSystemElapsedRealTime();
+ // complete to update TransportQuality for prev transport type
+ CallQualityBlock last = null;
+ int prevTransportType = QnsUtils.getOtherTransportType(dstTransportType);
+ TransportQuality prev = getLastTransportQuality(prevTransportType);
+ if (prev != null) {
+ last = prev.getLastCallQualityBlock();
+ }
+ // add a new TransportQuality for new transport type
+ mTransportQualityArray.get(dstTransportType)
+ .add(new TransportQuality(dstTransportType));
+ TransportQuality current = getLastTransportQuality(dstTransportType);
+ if (last != null) {
+ last.mDurationMillis = now - last.mCreatedElapsedTime;
+ current.mCallQualityBlockList
+ .add(new CallQualityBlock(last.mUpLinkLevel, last.mDownLinkLevel, now));
+ }
+ mLowQualityHandler.updateForHandover(dstTransportType);
+ }
+
+ private void startTrackingTransportQuality(int transportType) {
+ mTransportQualityArray.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).clear();
+ mTransportQualityArray.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).clear();
+ mTransportQualityArray.get(transportType)
+ .add(new TransportQuality(transportType));
+ }
+
+ void callStarted(@QnsConstants.QnsCallType int callType, int netCapability) {
+ if (mCallType != QnsConstants.CALL_TYPE_IDLE) {
+ if (mCallType != callType) {
+ callTypeUpdated(callType);
+ } else {
+ Log.w(mLogTag, "call type:" + callType + " already started.");
+ }
+ }
+ Log.d(mLogTag, "callStarted callType: " + callType + " netCapa:"
+ + QnsUtils.getNameOfNetCapability(netCapability));
+ mCallType = callType;
+ mNetCapability = netCapability;
+ //Transport type will be updated when EVENT_DATA_CONNECTION_STATUS_CHANGED occurs.
+ PreciseDataConnectionState dataState =
+ mTelephonyListener.getLastPreciseDataConnectionState(netCapability);
+ if (dataState != null && dataState.getTransportType()
+ != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
+ mTransportType = dataState.getTransportType();
+ startTrackingTransportQuality(mTransportType);
+ }
+ mTelephonyListener.registerPreciseDataConnectionStateChanged(mNetCapability,
+ mActiveCallHandler, EVENT_DATA_CONNECTION_STATUS_CHANGED, null, true);
+ }
+
+ private void callTypeUpdated(@QnsConstants.QnsCallType int callType) {
+ Log.d(mLogTag, "callTypeUpdated from " + mCallType + " to " + callType);
+ mCallType = callType;
+ }
+
+ void callEnded() {
+ mLowQualityHandler.exitLowQualityState();
+ long now = QnsUtils.getSystemElapsedRealTime();
+ // complete to update TransportQuality for prev transport type
+ CallQualityBlock last = null;
+ TransportQuality prev = getLastTransportQuality(mTransportType);
+ if (prev != null) {
+ last = prev.getLastCallQualityBlock();
+ }
+ if (last != null) {
+ last.mDurationMillis = now - last.mCreatedElapsedTime;
+ }
+ long upLinkQualityOverWwan = mActiveCallTracker
+ .getUpLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ long upLinkQualityOverWlan = mActiveCallTracker
+ .getUpLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+ long downLinkQualityOverWwan = mActiveCallTracker
+ .getDownLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ long downLinkQualityOverWlan = mActiveCallTracker
+ .getDownLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+ StringBuilder sb = new StringBuilder();
+ sb.append("CallQuality [WWAN:");
+ if (upLinkQualityOverWwan == QnsConstants.INVALID_VALUE
+ || downLinkQualityOverWwan == QnsConstants.INVALID_VALUE) {
+ sb.append("Not available] ");
+ } else {
+ sb.append("upLinkQualityOverWwan = ").append(upLinkQualityOverWwan)
+ .append(", downLinkQualityOverWwan = ").append(downLinkQualityOverWwan)
+ .append("] ");
+ }
+ sb.append("[WLAN:");
+ if (upLinkQualityOverWlan == QnsConstants.INVALID_VALUE
+ || downLinkQualityOverWlan == QnsConstants.INVALID_VALUE) {
+ sb.append("Not available] ");
+ } else {
+ sb.append("upLinkQualityOverWlan = ").append(upLinkQualityOverWwan)
+ .append(", downLinkQualityOverWlan = ").append(downLinkQualityOverWwan)
+ .append("] ");
+ }
+ Log.d(mLogTag, "callEnded callType: " + mCallType + " netCapa:"
+ + QnsUtils.getNameOfNetCapability(mNetCapability) + " " + sb.toString());
+ mCallType = QnsConstants.CALL_TYPE_IDLE;
+ mNetCapability = 0;
+ mAccessNetwork = AccessNetworkConstants.AccessNetworkType.UNKNOWN;
+ mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+ mTelephonyListener.unregisterPreciseDataConnectionStateChanged(
+ mNetCapability, mActiveCallHandler);
+ }
+
+ void onMediaQualityStatusChanged(MediaQualityStatus status) {
+ if (status == null) {
+ Log.e(mLogTag, "null MediaQualityStatus received.");
+ return;
+ }
+ Message msg = mLowQualityHandler
+ .obtainMessage(MediaLowQualityHandler.EVENT_MEDIA_QUALITY_CHANGED, status);
+ mLowQualityHandler.sendMessage(msg);
+ }
+
+ int getTransportType() {
+ return this.mTransportType;
+ }
+
+ int getCallType() {
+ return this.mCallType;
+ }
+
+ int getNetCapability() {
+ return this.mNetCapability;
+ }
+
+ @VisibleForTesting
+ TransportQuality getLastTransportQuality(int transportType) {
+ if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
+ Log.w(mLogTag, "getLastTransportQuality with invalid transport type.");
+ return null;
+ }
+ int size = mTransportQualityArray.get(transportType).size();
+ if (size > 0) {
+ return mTransportQualityArray.get(transportType).get(size - 1);
+ } else {
+ return null;
+ }
+ }
+
+ @VisibleForTesting
+ List<TransportQuality> getTransportQualityList(int transportType) {
+ return mTransportQualityArray.get(transportType);
+ }
+
+ long getUpLinkQualityLevelDuringCall(int transportType) {
+ List<TransportQuality> tqList = getTransportQualityList(transportType);
+ long sumUplinkQualityLevelVolume = 0;
+ long totalDuration = 0;
+ for (int i = 0; i < tqList.size(); i++) {
+ List<CallQualityBlock> callQualityBlockList = tqList.get(i).mCallQualityBlockList;
+ for (int j = 0; j < callQualityBlockList.size(); j++) {
+ CallQualityBlock cq = callQualityBlockList.get(j);
+ sumUplinkQualityLevelVolume += cq.getUpLinkQualityVolume();
+ long durationMillis = cq.mDurationMillis;
+ if (i == tqList.size() - 1 && j == callQualityBlockList.size() - 1) {
+ if (durationMillis == 0) {
+ durationMillis = QnsUtils.getSystemElapsedRealTime()
+ - cq.mCreatedElapsedTime;
+ }
+ }
+ if (durationMillis > 0) {
+ totalDuration += durationMillis;
+ } else {
+ return -1;
+ }
+ }
+ }
+ if (totalDuration <= 0) {
+ return QnsConstants.INVALID_VALUE;
+ }
+ long qualityLevel = sumUplinkQualityLevelVolume / totalDuration;
+ Log.d(mLogTag, "getUplinkQualityLevel for [" + AccessNetworkConstants
+ .transportTypeToString(transportType) + "] totalQualityVolume: "
+ + sumUplinkQualityLevelVolume + ", totalDuration: " + totalDuration
+ + " level:" + qualityLevel);
+ return qualityLevel;
+ }
+
+ long getDownLinkQualityLevelDuringCall(int transportType) {
+ List<TransportQuality> tqList = getTransportQualityList(transportType);
+ long sumDownLinkQualityLevelVolume = 0;
+ long totalDuration = 0;
+ for (int i = 0; i < tqList.size(); i++) {
+ List<CallQualityBlock> callQualityBlockList = tqList.get(i).mCallQualityBlockList;
+ for (int j = 0; j < callQualityBlockList.size(); j++) {
+ CallQualityBlock cq = callQualityBlockList.get(j);
+ sumDownLinkQualityLevelVolume += cq.getDownLinkQualityVolume();
+ long durationMillis = cq.mDurationMillis;
+ if (i == tqList.size() - 1 && j == callQualityBlockList.size() - 1) {
+ if (durationMillis == 0) {
+ durationMillis = QnsUtils.getSystemElapsedRealTime()
+ - cq.mCreatedElapsedTime;
+ }
+ }
+ if (durationMillis > 0) {
+ totalDuration += durationMillis;
+ } else {
+ return QnsConstants.INVALID_VALUE;
+ }
+ }
+ }
+ if (totalDuration <= 0) {
+ return QnsConstants.INVALID_VALUE;
+ }
+ long qualityLevel = sumDownLinkQualityLevelVolume / totalDuration;
+ Log.d(mLogTag, "getDownLinkQualityLevel for [" + AccessNetworkConstants
+ .transportTypeToString(transportType) + "] totalQualityVolume: "
+ + sumDownLinkQualityLevelVolume + ", totalDuration: " + totalDuration
+ + " level:" + qualityLevel);
+ return qualityLevel;
+ }
+
+ void updateCallQuality(CallState state) {
+ if (state == null) {
+ Log.w(mLogTag, "updateCallQuality Null CallState.");
+ return;
+ }
+ CallQuality cq = state.getCallQuality();
+ if (cq == null || isDummyCallQuality(cq)) {
+ return;
+ }
+ mActiveCallHandler.post(() -> onUpdateCallQuality(cq));
+ }
+
+ private void onUpdateCallQuality(CallQuality cq) {
+ long now = QnsUtils.getSystemElapsedRealTime();
+ CallQualityBlock prev = null;
+ TransportQuality transportQuality = getLastTransportQuality(mTransportType);
+ if (transportQuality != null) {
+ prev = transportQuality.getLastCallQualityBlock();
+ }
+ if (prev != null) {
+ prev.mDurationMillis = now - prev.mCreatedElapsedTime;
+ }
+ transportQuality.mCallQualityBlockList.add(
+ new CallQualityBlock(
+ cq.getUplinkCallQualityLevel(), cq.getDownlinkCallQualityLevel(), now));
+ }
+
+ private boolean isDummyCallQuality(CallQuality cq) {
+ return (cq.getNumRtpPacketsTransmitted() == 0
+ && cq.getNumRtpPacketsReceived() == 0
+ && cq.getUplinkCallQualityLevel() == 0
+ && cq.getDownlinkCallQualityLevel() == 0);
+ }
+ /**
+ * Register an event for low media quality report.
+ *
+ * @param h the Handler to get event.
+ * @param what the event.
+ * @param userObj user object.
+ */
+ void registerLowMediaQualityListener(
+ Handler h, int what, Object userObj) {
+ Log.d(mLogTag, "registerLowMediaQualityListener");
+ if (h != null) {
+ QnsRegistrant r = new QnsRegistrant(h, what, userObj);
+ mLowMediaQualityListeners.add(r);
+ }
+ }
+
+ /**
+ * Unregister an event for low media quality report.
+ *
+ * @param h the handler to get event.
+ */
+ void unregisterLowMediaQualityListener(Handler h) {
+ if (h != null) {
+ mLowMediaQualityListeners.remove(h);
+ }
+ }
+
+ int thresholdBreached(MediaQualityStatus status) {
+ int breachedReason = 0;
+ QnsCarrierConfigManager.RtpMetricsConfig rtpConfig = mConfigManager.getRTPMetricsData();
+ if (status.getRtpPacketLossRate() > 0
+ && status.getRtpPacketLossRate() > rtpConfig.mPktLossRate) {
+ breachedReason |= 1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS;
+ }
+ if (status.getRtpJitterMillis() > 0
+ && status.getRtpJitterMillis() > rtpConfig.mJitter) {
+ breachedReason |= 1 << QnsConstants.RTP_LOW_QUALITY_REASON_JITTER;
+ }
+ if (status.getRtpInactivityMillis() > 0
+ && status.getRtpInactivityMillis() > rtpConfig.mNoRtpInterval) {
+ breachedReason |= 1 << QnsConstants.RTP_LOW_QUALITY_REASON_NO_RTP;
+ }
+ return breachedReason;
+ }
+
+ boolean worseThanBefore(MediaQualityStatus before, MediaQualityStatus now) {
+ return thresholdBreached(now) > thresholdBreached(before);
+ }
+ }
+
+ QnsCallStatusTracker(QnsTelephonyListener telephonyListener,
+ QnsCarrierConfigManager configManager, int slotIndex) {
+ this(telephonyListener, configManager, slotIndex, null);
+ }
+
+ /** Only for test */
+ @VisibleForTesting
+ QnsCallStatusTracker(QnsTelephonyListener telephonyListener,
+ QnsCarrierConfigManager configManager, int slotIndex, Looper looper) {
mLogTag = QnsCallStatusTracker.class.getSimpleName() + "_" + slotIndex;
mTelephonyListener = telephonyListener;
+ mConfigManager = configManager;
+ mActiveCallTracker = new ActiveCallTracker(slotIndex, looper);
mTelephonyListener.addCallStatesChangedCallback(mCallStatesConsumer);
mTelephonyListener.addSrvccStateChangedCallback(mSrvccStateConsumer);
}
@@ -58,6 +742,9 @@ public class QnsCallStatusTracker {
void close() {
mTelephonyListener.removeCallStatesChangedCallback(mCallStatesConsumer);
mTelephonyListener.removeSrvccStateChangedCallback(mSrvccStateConsumer);
+ if (mActiveCallTracker != null) {
+ mActiveCallTracker.close();
+ }
}
void updateCallState(List<CallState> callStateList) {
@@ -82,7 +769,7 @@ public class QnsCallStatusTracker {
if (mLastNormalCallType != QnsConstants.CALL_TYPE_IDLE) {
mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE;
if (mCallTypeChangedEventListener != null) {
- mCallTypeChangedEventListener.notifyResult(mLastNormalCallType);
+ notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType);
}
}
if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE) {
@@ -90,11 +777,13 @@ public class QnsCallStatusTracker {
if (mEmergencyOverIms) {
mEmergencyOverIms = false;
if (mCallTypeChangedEventListener != null) {
- mCallTypeChangedEventListener.notifyResult(mLastNormalCallType);
+ notifyCallType(
+ NetworkCapabilities.NET_CAPABILITY_IMS, mLastEmergencyCallType);
}
} else {
if (mEmergencyCallTypeChangedEventListener != null) {
- mEmergencyCallTypeChangedEventListener.notifyResult(mLastEmergencyCallType);
+ notifyCallType(
+ NetworkCapabilities.NET_CAPABILITY_EIMS, mLastEmergencyCallType);
}
}
}
@@ -104,7 +793,7 @@ public class QnsCallStatusTracker {
&& !hasVideoCall() && !hasVoiceCall()) {
mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE;
if (mCallTypeChangedEventListener != null) {
- mCallTypeChangedEventListener.notifyResult(mLastNormalCallType);
+ notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType);
}
}
if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE && !hasEmergencyCall()) {
@@ -112,11 +801,13 @@ public class QnsCallStatusTracker {
if (mEmergencyOverIms) {
mEmergencyOverIms = false;
if (mCallTypeChangedEventListener != null) {
- mCallTypeChangedEventListener.notifyResult(mLastEmergencyCallType);
+ notifyCallType(
+ NetworkCapabilities.NET_CAPABILITY_IMS, mLastEmergencyCallType);
}
} else {
if (mEmergencyCallTypeChangedEventListener != null) {
- mEmergencyCallTypeChangedEventListener.notifyResult(mLastEmergencyCallType);
+ notifyCallType(
+ NetworkCapabilities.NET_CAPABILITY_EIMS, mLastEmergencyCallType);
}
}
}
@@ -126,25 +817,44 @@ public class QnsCallStatusTracker {
if (!isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_EIMS)
&& isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_IMS)) {
if (mCallTypeChangedEventListener != null) {
- mCallTypeChangedEventListener.notifyResult(mLastEmergencyCallType);
+ notifyCallType(
+ NetworkCapabilities.NET_CAPABILITY_IMS, mLastEmergencyCallType);
mEmergencyOverIms = true;
}
} else {
if (mEmergencyCallTypeChangedEventListener != null) {
- mEmergencyCallTypeChangedEventListener.notifyResult(mLastEmergencyCallType);
+ notifyCallType(
+ NetworkCapabilities.NET_CAPABILITY_EIMS, mLastEmergencyCallType);
}
}
} else if (hasVideoCall()) {
if (mLastNormalCallType != QnsConstants.CALL_TYPE_VIDEO) {
mLastNormalCallType = QnsConstants.CALL_TYPE_VIDEO;
- mCallTypeChangedEventListener.notifyResult(mLastNormalCallType);
+ notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType);
}
} else if (hasVoiceCall()) {
if (mLastNormalCallType != QnsConstants.CALL_TYPE_VOICE) {
mLastNormalCallType = QnsConstants.CALL_TYPE_VOICE;
- mCallTypeChangedEventListener.notifyResult(mLastNormalCallType);
+ notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType);
}
}
+ if (mActiveCallTracker.getCallType() != QnsConstants.CALL_TYPE_IDLE) {
+ mActiveCallTracker.updateCallQuality(getActiveCall());
+ }
+ }
+ }
+
+ private void notifyCallType(int netCapability, int callType) {
+ Log.d(mLogTag, "notifyCallType:" + netCapability + ", callType:" + callType);
+ if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
+ mCallTypeChangedEventListener.notifyResult(callType);
+ } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
+ mEmergencyCallTypeChangedEventListener.notifyResult(callType);
+ }
+ if (callType == QnsConstants.CALL_TYPE_IDLE) {
+ mActiveCallTracker.callEnded();
+ } else {
+ mActiveCallTracker.callStarted(callType, netCapability);
}
}
@@ -161,6 +871,15 @@ public class QnsCallStatusTracker {
return false;
}
+ CallState getActiveCall() {
+ for (CallState cs : mCallStates) {
+ if (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
+ return cs;
+ }
+ }
+ return null;
+ }
+
boolean hasVideoCall() {
for (CallState cs : mCallStates) {
if (cs.getImsCallServiceType() == ImsCallProfile.SERVICE_TYPE_NORMAL
@@ -234,6 +953,10 @@ public class QnsCallStatusTracker {
}
}
+ ActiveCallTracker getActiveCallTracker() {
+ return mActiveCallTracker;
+ }
+
@VisibleForTesting
void onSrvccStateChangedInternal(int srvccState) {
if (srvccState == TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED) {
@@ -260,6 +983,7 @@ public class QnsCallStatusTracker {
}
}
+
private boolean isDataNetworkConnected(int netCapability) {
PreciseDataConnectionState preciseDataStatus =
mTelephonyListener.getLastPreciseDataConnectionState(netCapability);