diff options
author | Sewook Seo <sewookseo@google.com> | 2023-01-04 06:05:15 +0000 |
---|---|---|
committer | Sewook Seo <sewookseo@google.com> | 2023-01-13 02:06:45 +0000 |
commit | c10e99f8add0785dd00f9f41a1a35049385e47ec (patch) | |
tree | 01d97f378e95c07fc9d73f4dfde82df1398717c7 /services/QualifiedNetworksService/src | |
parent | 1a460e3fb092f0a7389a873e994526f7e7fd1d9f (diff) | |
download | Telephony-c10e99f8add0785dd00f9f41a1a35049385e47ec.tar.gz |
[QNS] HO decision with media quality changed CB
QNS will use media quality status callback to avoid low quality
transport type during a call.
Bug: 264338274
Test: atest QualifiedNetworksServiceTests
Change-Id: I528ba652c21cd819c6d103eb0c34dfe8b71de4de
Diffstat (limited to 'services/QualifiedNetworksService/src')
7 files changed, 954 insertions, 119 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); diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCarrierConfigManager.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCarrierConfigManager.java index b0d1cf6..318e031 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCarrierConfigManager.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCarrierConfigManager.java @@ -495,28 +495,13 @@ class QnsCarrierConfigManager { "qns.minimum_handover_guarding_timer_ms_int"; /** - * List of 4 items indicating the RTP media metrics criteria to be set ,to make HO decision - * during Call. The three items part of integer array is jitter , Packet loss rate & RTP - * interval. + * This indicates time duration for packet loss rate sustained. * - * <p>{@link QnsConstants}. The values are set as below: - * - * <ul> - * <li>0: {@link QnsConstants#KEY_DEFAULT_JITTER} - * <li>1: {@link QnsConstants#KEY_DEFAULT_PACKET_LOSS_RATE} - * <li>2: {@link QnsConstants#KEY_DEFAULT_PACKET_LOSS_TIME_MILLIS} - * <li>3: {@link QnsConstants#KEY_DEFAULT_NO_RTP_INTERVAL_MILLIS} - * </ul> - * - * {@code QnsConstants#KEY_DEFAULT_JITTER , QnsConstants#KEY_DEFAULT_PACKET_LOSS_RATE , - * QnsConstants#KEY_DEFAULT_NO_RTP_INTERVAL }: If set , indicates the jitter : Indicating the - * latentcy , Packet loss rate : Indicating the percentage loss & RTP interval indicating the - * interval in secs criteria to determine the HO decision . The default value for this key is - * {@code QnsConstants#KEY_DEFAULT_JITTER, QnsConstants#KEY_DEFAULT_PACKET_LOSS_RATE, - * QnsConstants#KEY_DEFAULT_PACKET_LOSS_TIME_MILLIS, - * QnsConstants#KEY_DEFAULT_NO_RTP_INTERVAL_MILLIS} + * <p/> The default value for this key is {@code + * QnsConstants#KEY_DEFAULT_PACKET_LOSS_TIME_MILLIS} */ - static final String KEY_QNS_RTP_METRICS_INT_ARRAY = "qns.rtp_metrics_int_array"; + static final String KEY_QNS_MEDIA_THRESHOLD_RTP_PACKET_LOSS_TIME_MILLIS_INT = + "qns.media_threshold_rtp_packet_loss_time_millis"; /** * Specify a list of the waiting time(millisecond) for the preferred transport type when power @@ -752,7 +737,7 @@ class QnsCarrierConfigManager { private int[] mWlanHysteresisTimer; private int[] mNonImsWwanHysteresisTimer; private int[] mNonImsWlanHysteresisTimer; - private int[] mRTPMetricsData; + private int[] mRTPMetricsData = new int[4]; private int[] mWaitingTimerForPreferredTransport; private int[] mAllowMaxIwlanHoCountOnReason; private int[] mHoRestrictTimeOnRtpQuality; @@ -1173,6 +1158,8 @@ class QnsCarrierConfigManager { loadDirectFromCarrierConfigManagerKey(carrierConfigBundle); loadWfcConfigurations(carrierConfigBundle, assetConfigBundle); + + loadMediaThreshold(carrierConfigBundle, assetConfigBundle); } /** @@ -1364,7 +1351,6 @@ class QnsCarrierConfigManager { bundleCarrier, bundleAsset, KEY_QNS_HO_RESTRICT_TIME_WITH_LOW_RTP_QUALITY_MILLIS_INT_ARRAY); - mRTPMetricsData = getConfig(bundleCarrier, bundleAsset, KEY_QNS_RTP_METRICS_INT_ARRAY); mWlanRttBackhaulCheckConfigsOnPing = getConfig( bundleCarrier, @@ -1526,6 +1512,26 @@ class QnsCarrierConfigManager { } } + void loadMediaThreshold(PersistableBundle bundleCarrier, PersistableBundle assetConfigBundle) { + //read Jitter + mRTPMetricsData[0] = getConfig( + bundleCarrier, null, + CarrierConfigManager.ImsVoice.KEY_VOICE_RTP_JITTER_THRESHOLD_MILLIS_INT); + //read Packet Loss Rate + mRTPMetricsData[1] = getConfig( + bundleCarrier, null, + CarrierConfigManager.ImsVoice.KEY_VOICE_RTP_PACKET_LOSS_RATE_THRESHOLD_INT); + //read Inactivity Time + long inactivityTime = getConfig( + bundleCarrier, null, + CarrierConfigManager.ImsVoice.KEY_VOICE_RTP_INACTIVITY_TIME_THRESHOLD_MILLIS_LONG); + mRTPMetricsData[3] = (int) inactivityTime; + //read Packet Loss Duration + mRTPMetricsData[2] = getConfig( + bundleCarrier, assetConfigBundle, + KEY_QNS_MEDIA_THRESHOLD_RTP_PACKET_LOSS_TIME_MILLIS_INT); + } + /** Updated handover rules from carrier config. */ @VisibleForTesting List<HandoverRule> updateHandoverRules( @@ -2683,10 +2689,10 @@ class QnsCarrierConfigManager { /** RTP packet loss rate in percentage */ final int mPktLossRate; - /** Time interval of RTP packet loss rate */ + /** Time interval(milliseconds) of RTP packet loss rate */ final int mPktLossTime; - /** No RTP interval in seconds */ + /** No RTP interval in milliseconds */ final int mNoRtpInterval; RtpMetricsConfig(int jitter, int pktLossRate, int pktLossTime, int noRtpInterval) { diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java index ad9a2e0..4d33f5a 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java @@ -77,10 +77,6 @@ class QnsComponents { mCellularQualityMonitors.put( slotId, new CellularQualityMonitor(mContext, mQnsTelephonyListeners.get(slotId), slotId)); - mQnsCallStatusTracker.put( - slotId, - new QnsCallStatusTracker(mQnsTelephonyListeners.get(slotId), slotId)); - mQnsProvisioningListeners.put( slotId, new QnsProvisioningListener(mContext, mQnsImsManagers.get(slotId), slotId)); mQnsEventDispatchers.put( @@ -93,6 +89,10 @@ class QnsComponents { mQnsCarrierConfigManagers.put( slotId, new QnsCarrierConfigManager(mContext, mQnsEventDispatchers.get(slotId), slotId)); + mQnsCallStatusTracker.put( + slotId, + new QnsCallStatusTracker(mQnsTelephonyListeners.get(slotId), + mQnsCarrierConfigManagers.get(slotId), slotId)); mWifiBackhaulMonitors.put( slotId, new WifiBackhaulMonitor( @@ -238,6 +238,11 @@ class QnsComponents { mWifiBackhaulMonitors.remove(slotId); wifiBackhaulMonitor.close(); } + QnsCallStatusTracker qnsCallStatusTracker = mQnsCallStatusTracker.get(slotId); + if (qnsCallStatusTracker != null) { + mQnsCallStatusTracker.remove(slotId); + qnsCallStatusTracker.close(); + } QnsCarrierConfigManager qnsCarrierConfigManager = mQnsCarrierConfigManagers.get(slotId); if (qnsCarrierConfigManager != null) { mQnsCarrierConfigManagers.remove(slotId); @@ -253,11 +258,6 @@ class QnsComponents { mQnsProvisioningListeners.remove(slotId); qnsProvisioningListener.close(); } - QnsCallStatusTracker qnsCallStatusTracker = mQnsCallStatusTracker.get(slotId); - if (qnsCallStatusTracker != null) { - mQnsCallStatusTracker.remove(slotId); - qnsCallStatusTracker.close(); - } CellularQualityMonitor cellularQualityMonitor = mCellularQualityMonitors.get(slotId); if (cellularQualityMonitor != null) { mCellularQualityMonitors.remove(slotId); diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsConstants.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsConstants.java index 065aaee..ba2c2f1 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsConstants.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsConstants.java @@ -29,6 +29,7 @@ class QnsConstants { static final String QNS_TAG = "QNS"; + static final int INVALID_VALUE = -1; static final int INVALID_ID = -1; static final int INVALID_SUB_ID = -1; static final int KEY_DEFAULT_VALUE = 0; @@ -37,10 +38,7 @@ class QnsConstants { static final int CONFIG_DEFAULT_MIN_HANDOVER_GUARDING_TIMER = 0; static final int CONFIG_DEFAULT_MIN_HANDOVER_GUARDING_TIMER_LIMIT = 5000; - static final int KEY_DEFAULT_JITTER = 120; - static final int KEY_DEFAULT_PACKET_LOSS_RATE = 40; static final int KEY_DEFAULT_PACKET_LOSS_TIME_MILLIS = 5000; - static final int KEY_DEFAULT_NO_RTP_INTERVAL_MILLIS = 2000; static final int KEY_DEFAULT_IWLAN_AVOID_TIME_LOW_RTP_QUALITY_MILLIS = 60000; static final int KEY_DEFAULT_THRESHOLD_SSRSRP_GOOD = -99; @@ -77,6 +75,8 @@ class QnsConstants { @IntDef(value = {COVERAGE_HOME, COVERAGE_ROAM, COVERAGE_BOTH}) @interface CellularCoverage {} + // These(RTP_LOW_QUALITY_) constants are used to @code worseThanBefore(), + // be caution before change. static final int RTP_LOW_QUALITY_REASON_JITTER = 1; static final int RTP_LOW_QUALITY_REASON_PACKET_LOSS = 2; static final int RTP_LOW_QUALITY_REASON_NO_RTP = 3; diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTelephonyListener.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTelephonyListener.java index e890cf1..0945be6 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTelephonyListener.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTelephonyListener.java @@ -37,6 +37,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.telephony.VopsSupportInfo; +import android.telephony.ims.MediaQualityStatus; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -69,6 +70,7 @@ class QnsTelephonyListener { QnsRegistrantList mIwlanServiceStateListener = new QnsRegistrantList(); List<Consumer<List<CallState>>> mCallStatesConsumerList = new ArrayList<>(); List<Consumer<Integer>> mSrvccStateConsumerList = new ArrayList<>(); + List<Consumer<MediaQualityStatus>> mMediaQualityConsumerList = new ArrayList<>(); protected HashMap<Integer, QnsRegistrantList> mQnsTelephonyInfoRegistrantMap = new HashMap<>(); protected HashMap<Integer, QnsRegistrantList> mNetCapabilityRegistrantMap = new HashMap<>(); protected QnsTelephonyInfo mLastQnsTelephonyInfo = new QnsTelephonyInfo(); @@ -1025,7 +1027,8 @@ class QnsTelephonyListener { TelephonyCallback.BarringInfoListener, TelephonyCallback.CallStateListener, TelephonyCallback.SrvccStateListener, - TelephonyCallback.CallAttributesListener { + TelephonyCallback.CallAttributesListener, + TelephonyCallback.MediaQualityStatusChangedListener { private final Executor mExecutor; private OnServiceStateListener mServiceStateListener; private OnPreciseDataConnectionStateListener mPreciseDataConnectionStateListener; @@ -1149,6 +1152,13 @@ class QnsTelephonyListener { mCallStatesCallback.onCallStatesChanged(callStateList); } } + + @Override + public void onMediaQualityStatusChanged(MediaQualityStatus status) { + for (Consumer<MediaQualityStatus> consumer : mMediaQualityConsumerList) { + consumer.accept(status); + } + } } void addCallStatesChangedCallback(Consumer<List<CallState>> consumer) { @@ -1167,6 +1177,14 @@ class QnsTelephonyListener { mSrvccStateConsumerList.remove(consumer); } + void addMediaQualityStatusCallback(Consumer<MediaQualityStatus> consumer) { + mMediaQualityConsumerList.add(consumer); + } + + void removeMediaQualityStatusCallback(Consumer<MediaQualityStatus> consumer) { + mMediaQualityConsumerList.remove(consumer); + } + /** * Dumps the state of {@link QualityMonitor} * diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsUtils.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsUtils.java index 5535bfa..c9745e4 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsUtils.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsUtils.java @@ -264,6 +264,17 @@ class QnsUtils { return false; } + @AccessNetworkConstants.TransportType + static int getTransportTypeFromAccessNetwork( + @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork) { + if (accessNetwork == AccessNetworkConstants.AccessNetworkType.IWLAN) { + return AccessNetworkConstants.TRANSPORT_TYPE_WLAN; + } else if (accessNetwork != AccessNetworkConstants.AccessNetworkType.UNKNOWN) { + return AccessNetworkConstants.TRANSPORT_TYPE_WWAN; + } + return AccessNetworkConstants.TRANSPORT_TYPE_INVALID; + } + /** * Get Set of network capabilities from string joined by {@code |}, space is ignored. If input * string contains unknown capability or malformatted(e.g. empty string), -1 is included in the @@ -285,6 +296,21 @@ class QnsUtils { } /** + * Returns another transport type. + * @param transportType transport type + * @return another transport type of input parameter + */ + @AccessNetworkConstants.TransportType + static int getOtherTransportType(@AccessNetworkConstants.TransportType int transportType) { + if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { + return AccessNetworkConstants.TRANSPORT_TYPE_WWAN; + } else if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { + return AccessNetworkConstants.TRANSPORT_TYPE_WLAN; + } + return AccessNetworkConstants.TRANSPORT_TYPE_INVALID; + } + + /** * Convert network capabilities to string. * * <p>This is for debugging and logging purposes only. @@ -608,6 +634,13 @@ class QnsUtils { return (T) Integer.valueOf(QnsConstants.RAT_PREFERENCE_DEFAULT); case KEY_QNS_VOWIFI_REGISTATION_TIMER_FOR_VOWIFI_ACTIVATION_INT: return (T) Integer.valueOf(CONFIG_DEFAULT_VOWIFI_REGISTATION_TIMER); + case CarrierConfigManager.ImsVoice.KEY_VOICE_RTP_JITTER_THRESHOLD_MILLIS_INT: + case CarrierConfigManager.ImsVoice.KEY_VOICE_RTP_PACKET_LOSS_RATE_THRESHOLD_INT: + return (T) Integer.valueOf(QnsConstants.INVALID_VALUE); + case QnsCarrierConfigManager.KEY_QNS_MEDIA_THRESHOLD_RTP_PACKET_LOSS_TIME_MILLIS_INT: + return (T) Integer.valueOf(QnsConstants.KEY_DEFAULT_PACKET_LOSS_TIME_MILLIS); + case CarrierConfigManager.ImsVoice.KEY_VOICE_RTP_INACTIVITY_TIME_THRESHOLD_MILLIS_LONG: + return (T) Long.valueOf(QnsConstants.INVALID_VALUE); case QnsCarrierConfigManager .KEY_QNS_IN_CALL_ROVEIN_ALLOWED_COUNT_AND_FALLBACK_REASON_INT_ARRAY: return (T) @@ -639,14 +672,6 @@ class QnsUtils { }; case QnsCarrierConfigManager.KEY_MINIMUM_HANDOVER_GUARDING_TIMER_MS_INT: return (T) Integer.valueOf(QnsConstants.CONFIG_DEFAULT_MIN_HANDOVER_GUARDING_TIMER); - case QnsCarrierConfigManager.KEY_QNS_RTP_METRICS_INT_ARRAY: - return (T) - new int[] { - QnsConstants.KEY_DEFAULT_JITTER, - QnsConstants.KEY_DEFAULT_PACKET_LOSS_RATE, - QnsConstants.KEY_DEFAULT_PACKET_LOSS_TIME_MILLIS, - QnsConstants.KEY_DEFAULT_NO_RTP_INTERVAL_MILLIS - }; case QnsCarrierConfigManager .KEY_CHOOSE_WFC_PREFERRED_TRANSPORT_IN_BOTH_BAD_CONDITION_INT_ARRAY: return (T) new int[] {}; diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java index c49f674..0837f00 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java @@ -96,9 +96,8 @@ class RestrictManager { private static final int EVENT_IMS_REGISTRATION_STATE_CHANGED = 3004; private static final int EVENT_LOW_RTP_QUALITY_REPORTED = 3006; private static final int EVENT_RELEASE_RESTRICTION = 3008; - private static final int EVENT_REGISTER_LOW_RTP_QUALITY = 3009; - protected static final int EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED = 3010; - private static final int EVENT_WIFI_RTT_BACKHAUL_CHECK_STATUS = 3011; + protected static final int EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED = 3009; + private static final int EVENT_WIFI_RTT_BACKHAUL_CHECK_STATUS = 3010; @VisibleForTesting static final int GUARDING_TIMER_HANDOVER_INIT = 30000; @@ -159,7 +158,8 @@ class RestrictManager { @VisibleForTesting QnsRegistrant mRestrictInfoRegistrant; private DataConnectionStatusTracker mDataConnectionStatusTracker; private CellularNetworkStatusTracker mCellularNetworkStatusTracker; - private AlternativeEventListener mAltEventListener; + private QnsCallStatusTracker mQnsCallStatusTracker; + private QnsCallStatusTracker.ActiveCallTracker mActiveCallTracker; private QnsImsManager mQnsImsManager; private WifiBackhaulMonitor mWifiBackhaulMonitor; private int mNetCapability; @@ -215,7 +215,7 @@ class RestrictManager { ar = (QnsAsyncResult) message.obj; int reason = (int) ar.mResult; Log.d(mLogTag, "EVENT_LOW_RTP_QUALITY_REPORTED reason: " + reason); - onLowRtpQualityEvent(); + onLowRtpQualityEvent(reason); break; case EVENT_IMS_REGISTRATION_STATE_CHANGED: @@ -241,13 +241,6 @@ class RestrictManager { } break; - case EVENT_REGISTER_LOW_RTP_QUALITY: - int currTransportType = message.arg1; - if (currTransportType == mTransportType) { - registerLowRtpQualityEvent(); - } - break; - case EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED: Log.d( mLogTag, @@ -255,7 +248,7 @@ class RestrictManager { + mIsTimerRunningOnDataConnectionFail); if (mIsTimerRunningOnDataConnectionFail) { - currTransportType = message.arg1; + int currTransportType = message.arg1; fallbackToOtherTransportOnDataConnectionFail(currTransportType); } break; @@ -316,6 +309,17 @@ class RestrictManager { } } + class LowRtpQualityRestriction extends Restriction{ + private int mReason; + LowRtpQualityRestriction(int type, int[] releaseEvents, int restrictTime, int reason) { + super(type, releaseEvents, restrictTime); + mReason = reason; + } + int getReason() { + return mReason; + } + } + class Restriction { private final int mRestrictType; final ArrayList<Integer> mReleaseEventList; @@ -444,7 +448,8 @@ class RestrictManager { mHandler = new RestrictManagerHandler(loop); mNetCapability = netCapability; mDataConnectionStatusTracker = dcst; - mAltEventListener = qnsComponents.getAlternativeEventListener(mSlotId); + mQnsCallStatusTracker = qnsComponents.getQnsCallStatusTracker(mSlotId); + mActiveCallTracker = qnsComponents.getQnsCallStatusTracker(mSlotId).getActiveCallTracker(); mDataConnectionStatusTracker.registerDataConnectionStatusChanged( mHandler, EVENT_DATA_CONNECTION_CHANGED); if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) { @@ -500,7 +505,9 @@ class RestrictManager { mQnsEventDispatcher.unregisterEvent(mHandler); if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS || mNetCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) { - mAltEventListener.unregisterLowRtpQualityEvent(mNetCapability, mHandler); + if (mActiveCallTracker != null) { + mActiveCallTracker.unregisterLowMediaQualityListener(mHandler); + } } if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) { mQnsImsManager.unregisterImsRegistrationStatusChanged(mHandler); @@ -539,7 +546,7 @@ class RestrictManager { mQnsCarrierConfigManager.getWaitingTimerForPreferredTransportOnPowerOn( transportType); if (waitingTimer != QnsConstants.KEY_DEFAULT_VALUE) { - int preventTransportType = getOtherTransport(transportType); + int preventTransportType = QnsUtils.getOtherTransportType(transportType); Log.d( mLogTag, "prevent " @@ -628,25 +635,53 @@ class RestrictManager { } @VisibleForTesting - void onLowRtpQualityEvent() { + void onLowRtpQualityEvent(@QnsConstants.RtpLowQualityReason int reason) { int lowRtpQualityRestrictTime = mQnsCarrierConfigManager.getHoRestrictedTimeOnLowRTPQuality(mTransportType); if ((mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN || mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) && lowRtpQualityRestrictTime > 0 - && mImsCallType != QnsConstants.CALL_TYPE_IDLE) { - addRestriction( - mTransportType, - RESTRICT_TYPE_RTP_LOW_QUALITY, - sReleaseEventMap.get(RESTRICT_TYPE_RTP_LOW_QUALITY), - lowRtpQualityRestrictTime); - unregisterLowRtpQualityEvent(); - - if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { - int fallbackReason = mQnsCarrierConfigManager.getQnsIwlanHoRestrictReason(); - if (fallbackReason == QnsConstants.FALLBACK_REASON_RTP_OR_WIFI - || fallbackReason == QnsConstants.FALLBACK_REASON_RTP_ONLY) { - increaseCounterToRestrictIwlanInCall(); + && (mImsCallType == QnsConstants.CALL_TYPE_VOICE + || mImsCallType == QnsConstants.CALL_TYPE_EMERGENCY)) { + if (reason > 0) { + Restriction restriction = + new LowRtpQualityRestriction(RESTRICT_TYPE_RTP_LOW_QUALITY, + sReleaseEventMap.get(RESTRICT_TYPE_RTP_LOW_QUALITY), + lowRtpQualityRestrictTime, + reason); + // If current report has 'no RTP reason' and previous report at previous + // transport type doesn't have 'no RTP reason', let's move back to previous + // transport type. + if ((reason & 1 << QnsConstants.RTP_LOW_QUALITY_REASON_NO_RTP) != 0) { + HashMap<Integer, Restriction> restrictionMap = mRestrictInfos + .get(QnsUtils.getOtherTransportType(mTransportType)) + .getRestrictionMap(); + Restriction restrictionOtherSide = restrictionMap.get( + RESTRICT_TYPE_RTP_LOW_QUALITY); + if (restrictionOtherSide != null + && restrictionOtherSide instanceof LowRtpQualityRestriction) { + int reasonOtherSide = + ((LowRtpQualityRestriction) restrictionOtherSide).getReason(); + if ((reasonOtherSide & 1 << QnsConstants.RTP_LOW_QUALITY_REASON_NO_RTP) + == 0) { + releaseRestriction(QnsUtils.getOtherTransportType(mTransportType), + RESTRICT_TYPE_RTP_LOW_QUALITY, true); + } + } + } + // If both transport have low RTP quality restriction, let ANE do final decision. + addRestriction(mTransportType, restriction, lowRtpQualityRestrictTime); + + if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { + int fallbackReason = mQnsCarrierConfigManager.getQnsIwlanHoRestrictReason(); + if (fallbackReason == QnsConstants.FALLBACK_REASON_RTP_OR_WIFI + || fallbackReason == QnsConstants.FALLBACK_REASON_RTP_ONLY) { + increaseCounterToRestrictIwlanInCall(); + } + } + } else { + if (hasRestrictionType(mTransportType, RESTRICT_TYPE_RTP_LOW_QUALITY)) { + releaseRestriction(mTransportType, RESTRICT_TYPE_RTP_LOW_QUALITY); } } } @@ -695,7 +730,7 @@ class RestrictManager { checkToCancelInitialPdnConnectionFailFallback(); clearInitialPdnConnectionFailFallbackRestriction(); - checkIfCancelNonPreferredRestriction(getOtherTransport(transportType)); + checkIfCancelNonPreferredRestriction(QnsUtils.getOtherTransportType(transportType)); if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) { if (mLastEvaluatedTransportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID || transportType == mLastEvaluatedTransportType) { @@ -794,12 +829,6 @@ class RestrictManager { // Return to the transport type restricted by low RTP. It may be singleRAT case, release // the restriction. releaseRestriction(mTransportType, RESTRICT_TYPE_RTP_LOW_QUALITY); - Log.d(mLogTag, "Unregister & Register Low RTP quality for " + mTransportType); - unregisterLowRtpQualityEvent(); - - Message msg = - mHandler.obtainMessage(EVENT_REGISTER_LOW_RTP_QUALITY, mTransportType, 0, null); - mHandler.sendMessageDelayed(msg, (long) QnsConstants.DEFAULT_MSG_DELAY_TIMER); } } @@ -808,7 +837,7 @@ class RestrictManager { } private void processHandoverGuardingOperation(int transportType) { - int guardingTransport = getOtherTransport(transportType); + int guardingTransport = QnsUtils.getOtherTransportType(transportType); int delayMillis = getGuardingTimeMillis(guardingTransport, mImsCallType); int minimumGuardingTimer = mQnsCarrierConfigManager.getMinimumHandoverGuardingTimer(); if (delayMillis == 0 && minimumGuardingTimer > 0) { @@ -1031,7 +1060,7 @@ class RestrictManager { int transportType, int restrictType, int fallbackTimeMillis) { Log.d(mLogTag, "release ignorable restrictions on WWAN to fallback."); for (int restriction : ignorableRestrictionsOnSingleRat) { - releaseRestriction(getOtherTransport(transportType), restriction, false); + releaseRestriction(QnsUtils.getOtherTransportType(transportType), restriction, false); } addRestriction( transportType, @@ -1050,7 +1079,8 @@ class RestrictManager { } mLastEvaluatedTransportType = transportType; if (mDataConnectionStatusTracker.isActiveState() && mTransportType != transportType) { - startGuarding(GUARDING_TIMER_HANDOVER_INIT, getOtherTransport(transportType)); + startGuarding(GUARDING_TIMER_HANDOVER_INIT, + QnsUtils.getOtherTransportType(transportType)); } } @@ -1082,7 +1112,7 @@ class RestrictManager { } private void updateGuardingTimerConditionOnCallState(int prevCallType, int newCallType) { - int currGuardingTransport = getOtherTransport(mTransportType); + int currGuardingTransport = QnsUtils.getOtherTransportType(mTransportType); if (mRestrictInfos.get(currGuardingTransport) == null) return; HashMap<Integer, Restriction> restrictionMap = @@ -1141,6 +1171,49 @@ class RestrictManager { } } + void addRestriction(int transport, Restriction restrictObj, int timeMillis) { + boolean needNotify = false; + HashMap<Integer, Restriction> restrictionMap = + mRestrictInfos.get(transport).getRestrictionMap(); + Restriction restriction = restrictionMap.get(restrictObj.mRestrictType); + Log.d( + mLogTag, + "addRestriction[" + + QnsConstants.transportTypeToString(transport) + + "] " + + restrictTypeToString(restrictObj.mRestrictType) + + " was restrict:" + + (restriction != null)); + if (restriction == null) { + restriction = restrictObj; + restrictionMap.put(restrictObj.mRestrictType, restriction); + Log.d( + mLogTag, + "addRestriction[" + + QnsConstants.transportTypeToString(transport) + + "] " + + restriction); + needNotify = true; + } else { + if (timeMillis > 0) { + restriction.updateRestrictTime(timeMillis); + removeReleaseRestrictionMessage(restriction); + } + Log.d( + mLogTag, + "updateRestriction[" + + QnsConstants.transportTypeToString(transport) + + "] " + + restriction); + } + if (timeMillis > 0) { + sendReleaseRestrictionMessage(transport, restriction); + } + if (needNotify) { + notifyRestrictInfoChanged(); + } + } + void addRestriction(int transport, int type, int[] releaseEvents, int timeMillis) { boolean needNotify = false; HashMap<Integer, Restriction> restrictionMap = @@ -1261,14 +1334,6 @@ class RestrictManager { mHandler.removeMessages(EVENT_RELEASE_RESTRICTION, restriction); } - protected int getOtherTransport(int transportType) { - if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { - return AccessNetworkConstants.TRANSPORT_TYPE_WWAN; - } else { - return AccessNetworkConstants.TRANSPORT_TYPE_WLAN; - } - } - void registerRestrictInfoChanged(Handler h, int what) { mRestrictInfoRegistrant = new QnsRegistrant(h, what, null); } @@ -1375,17 +1440,13 @@ class RestrictManager { || mImsCallType == QnsConstants.CALL_TYPE_EMERGENCY) && (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN || mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) - && mAltEventListener != null) { + && mActiveCallTracker != null) { int hoRestrictTimeOnLowRtpQuality = mQnsCarrierConfigManager.getHoRestrictedTimeOnLowRTPQuality(mTransportType); if (hoRestrictTimeOnLowRtpQuality > 0) { Log.d(mLogTag, "registerLowRtpQualityEvent"); - mAltEventListener.registerLowRtpQualityEvent( - mNetCapability, - mHandler, - EVENT_LOW_RTP_QUALITY_REPORTED, - null, - mQnsCarrierConfigManager.getRTPMetricsData()); + mActiveCallTracker.registerLowMediaQualityListener( + mHandler, EVENT_LOW_RTP_QUALITY_REPORTED, null); } } } @@ -1393,7 +1454,9 @@ class RestrictManager { private void unregisterLowRtpQualityEvent() { if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS || mNetCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) { - mAltEventListener.unregisterLowRtpQualityEvent(mNetCapability, mHandler); + if (mActiveCallTracker != null) { + mActiveCallTracker.unregisterLowMediaQualityListener(mHandler); + } } } @@ -1444,9 +1507,7 @@ class RestrictManager { case NetworkCapabilities.NET_CAPABILITY_MMS: case NetworkCapabilities.NET_CAPABILITY_XCAP: case NetworkCapabilities.NET_CAPABILITY_CBS: - callType = - mAltEventListener.isIdleState() - ? QnsConstants.CALL_TYPE_IDLE + callType = mQnsCallStatusTracker.isCallIdle() ? QnsConstants.CALL_TYPE_IDLE : QnsConstants.CALL_TYPE_VOICE; if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { delayMillis = @@ -1484,14 +1545,15 @@ class RestrictManager { // Transport // Type if (transportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID - && hasRestrictionType( - getOtherTransport(transportType), RestrictManager.RESTRICT_TYPE_GUARDING)) { + && hasRestrictionType(QnsUtils.getOtherTransportType(transportType), + RestrictManager.RESTRICT_TYPE_GUARDING)) { Log.d( mLogTag, "RESTRICT_TYPE_GUARDING cleared from Guarding for:" + QnsConstants.transportTypeToString(mTransportType)); // addRestriction() will take care to notify the ANE of Restrict Info status - releaseRestriction(getOtherTransport(transportType), RESTRICT_TYPE_GUARDING, true); + releaseRestriction( + QnsUtils.getOtherTransportType(transportType), RESTRICT_TYPE_GUARDING, true); } addRestriction( |