blob: beb316b12498423526275b16f2fb70a290fe3a8d [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.telephony.qns;
import static com.android.telephony.qns.DataConnectionStatusTracker.STATE_CONNECTED;
import static com.android.telephony.qns.DataConnectionStatusTracker.STATE_HANDOVER;
import android.annotation.IntDef;
import android.net.NetworkCapabilities;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.telephony.qns.DataConnectionStatusTracker.DataConnectionChangedInfo;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Prevents HO pingpong between Cellular and IWLAN. Provide Throttling for certain cause. Provide
* Handover not allowed policy.
*/
class RestrictManager {
private final String mLogTag;
private final boolean mDebugFlag = true;
static final int RESTRICT_TYPE_GUARDING = 1;
static final int RESTRICT_TYPE_THROTTLING = 2;
static final int RESTRICT_TYPE_HO_NOT_ALLOWED = 3;
static final int RESTRICT_TYPE_NON_PREFERRED_TRANSPORT = 4;
static final int RESTRICT_TYPE_RTP_LOW_QUALITY = 5;
static final int RESTRICT_TYPE_RESTRICT_IWLAN_IN_CALL = 6;
static final int RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL = 7;
static final int RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL = 8;
static final int RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL = 9;
static final int RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL = 10;
@IntDef(
value = {
RESTRICT_TYPE_GUARDING,
RESTRICT_TYPE_THROTTLING,
RESTRICT_TYPE_HO_NOT_ALLOWED,
RESTRICT_TYPE_NON_PREFERRED_TRANSPORT,
RESTRICT_TYPE_RTP_LOW_QUALITY,
RESTRICT_TYPE_RESTRICT_IWLAN_IN_CALL,
RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL,
RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL,
RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL,
RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL,
})
@interface RestrictType {}
static final int RELEASE_EVENT_DISCONNECT = 1;
static final int RELEASE_EVENT_WIFI_AP_CHANGED = 2;
static final int RELEASE_EVENT_WFC_PREFER_MODE_CHANGED = 3;
static final int RELEASE_EVENT_CALL_END = 4;
static final int RELEASE_EVENT_IMS_NOT_SUPPORT_RAT = 5;
@IntDef(
value = {
RELEASE_EVENT_DISCONNECT,
RELEASE_EVENT_WIFI_AP_CHANGED,
RELEASE_EVENT_WFC_PREFER_MODE_CHANGED,
RELEASE_EVENT_CALL_END,
RELEASE_EVENT_IMS_NOT_SUPPORT_RAT,
})
@interface ReleaseEvent {}
private static final int EVENT_DATA_CONNECTION_CHANGED = 3001;
private static final int EVENT_CALL_STATE_CHANGED = 3002;
private static final int EVENT_SRVCC_STATE_CHANGED = 3003;
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;
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;
static final HashMap<Integer, int[]> sReleaseEventMap =
new HashMap<Integer, int[]>() {
{
put(
RESTRICT_TYPE_GUARDING,
new int[] {
RELEASE_EVENT_DISCONNECT, RELEASE_EVENT_WFC_PREFER_MODE_CHANGED
});
put(
RESTRICT_TYPE_RTP_LOW_QUALITY,
new int[] {RELEASE_EVENT_CALL_END, RELEASE_EVENT_WIFI_AP_CHANGED});
put(RESTRICT_TYPE_RESTRICT_IWLAN_IN_CALL, new int[] {RELEASE_EVENT_CALL_END});
put(
RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL,
new int[] {
RELEASE_EVENT_DISCONNECT, RELEASE_EVENT_IMS_NOT_SUPPORT_RAT
});
put(
RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL,
new int[] {
RELEASE_EVENT_DISCONNECT,
RELEASE_EVENT_WIFI_AP_CHANGED,
RELEASE_EVENT_WFC_PREFER_MODE_CHANGED,
RELEASE_EVENT_IMS_NOT_SUPPORT_RAT
});
put(
RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL,
new int[] {
RELEASE_EVENT_DISCONNECT,
RELEASE_EVENT_WIFI_AP_CHANGED,
RELEASE_EVENT_IMS_NOT_SUPPORT_RAT
});
}
};
private static final int[] ignorableRestrictionsOnSingleRat =
new int[] {
RESTRICT_TYPE_GUARDING,
RESTRICT_TYPE_RTP_LOW_QUALITY,
RESTRICT_TYPE_RESTRICT_IWLAN_IN_CALL,
RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL,
RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL,
RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL
};
private QnsCarrierConfigManager mQnsCarrierConfigManager;
private QnsTelephonyListener mTelephonyListener;
private QnsEventDispatcher mQnsEventDispatcher;
@VisibleForTesting Handler mHandler;
@QnsConstants.CellularCoverage
int mCellularCoverage; // QnsConstants.COVERAGE_HOME or QnsConstants.COVERAGE_ROAM
int mCellularAccessNetwork;
@VisibleForTesting QnsRegistrant mRestrictInfoRegistrant;
private DataConnectionStatusTracker mDataConnectionStatusTracker;
private CellularNetworkStatusTracker mCellularNetworkStatusTracker;
private QnsCallStatusTracker mQnsCallStatusTracker;
private QnsCallStatusTracker.ActiveCallTracker mActiveCallTracker;
private QnsImsManager mQnsImsManager;
private WifiBackhaulMonitor mWifiBackhaulMonitor;
private int mNetCapability;
private int mSlotId;
private int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
private int mLastEvaluatedTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
private int mWfcPreference;
private int mWfcRoamingPreference;
private int mCounterForIwlanRestrictionInCall;
private int mRetryCounterOnDataConnectionFail;
private int mFallbackCounterOnDataConnectionFail;
private boolean mIsRttStatusCheckRegistered = false;
private int mLastDataConnectionTransportType;
private boolean mIsTimerRunningOnDataConnectionFail = false;
private Pair<Integer, Long> mDeferredThrottlingEvent = null;
/** IMS call type */
@QnsConstants.QnsCallType private int mImsCallType;
/** Call state from TelephonyCallback.CallStateListener */
@Annotation.CallState private int mCallState;
private Map<Integer, RestrictInfo> mRestrictInfos = new ConcurrentHashMap<>();
private class RestrictManagerHandler extends Handler {
RestrictManagerHandler(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_CHANGED:
ar = (QnsAsyncResult) message.obj;
onDataConnectionChanged((DataConnectionChangedInfo) ar.mResult);
break;
case EVENT_CALL_STATE_CHANGED:
ar = (QnsAsyncResult) message.obj;
int callState = (int) ar.mResult;
onCallStateChanged(callState, mTransportType, mCellularAccessNetwork);
break;
case EVENT_SRVCC_STATE_CHANGED:
ar = (QnsAsyncResult) message.obj;
int srvccState = (int) ar.mResult;
onSrvccStateChanged(srvccState);
break;
case EVENT_LOW_RTP_QUALITY_REPORTED:
ar = (QnsAsyncResult) message.obj;
int reason = (int) ar.mResult;
Log.d(mLogTag, "EVENT_LOW_RTP_QUALITY_REPORTED reason: " + reason);
onLowRtpQualityEvent(reason);
break;
case EVENT_IMS_REGISTRATION_STATE_CHANGED:
ar = (QnsAsyncResult) message.obj;
onImsRegistrationStateChanged((QnsImsManager.ImsRegistrationState) ar.mResult);
break;
case EVENT_RELEASE_RESTRICTION:
transportType = message.arg1;
Restriction restriction = (Restriction) message.obj;
Log.d(
mLogTag,
"EVENT_RELEASE_RESTRICTION : "
+ QnsConstants.transportTypeToString(transportType)
+ " "
+ restrictTypeToString(restriction.mRestrictType));
if (restriction
== mRestrictInfos
.get(transportType)
.getRestrictionMap()
.get(restriction.mRestrictType)) {
releaseRestriction(transportType, restriction.mRestrictType);
}
break;
case EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED:
Log.d(
mLogTag,
"Initial Data Connection fail timer expired"
+ mIsTimerRunningOnDataConnectionFail);
if (mIsTimerRunningOnDataConnectionFail) {
int currTransportType = message.arg1;
fallbackToOtherTransportOnDataConnectionFail(currTransportType);
}
break;
case EVENT_WIFI_RTT_BACKHAUL_CHECK_STATUS:
ar = (QnsAsyncResult) message.obj;
boolean rttCheckStatus = (boolean) ar.mResult;
if (!rttCheckStatus) { // rtt Backhaul check failed
Log.d(mLogTag, "Rtt check status received:Fail");
onWlanRttFail();
}
break;
case QnsEventDispatcher.QNS_EVENT_WFC_MODE_TO_WIFI_ONLY:
onWfcModeChanged(QnsConstants.WIFI_ONLY, QnsConstants.COVERAGE_HOME);
break;
case QnsEventDispatcher.QNS_EVENT_WFC_MODE_TO_CELLULAR_PREFERRED:
onWfcModeChanged(QnsConstants.CELL_PREF, QnsConstants.COVERAGE_HOME);
break;
case QnsEventDispatcher.QNS_EVENT_WFC_MODE_TO_WIFI_PREFERRED:
onWfcModeChanged(QnsConstants.WIFI_PREF, QnsConstants.COVERAGE_HOME);
break;
case QnsEventDispatcher.QNS_EVENT_WFC_ROAMING_MODE_TO_WIFI_ONLY:
onWfcModeChanged(QnsConstants.WIFI_ONLY, QnsConstants.COVERAGE_ROAM);
break;
case QnsEventDispatcher.QNS_EVENT_WFC_ROAMING_MODE_TO_CELLULAR_PREFERRED:
onWfcModeChanged(QnsConstants.CELL_PREF, QnsConstants.COVERAGE_ROAM);
break;
case QnsEventDispatcher.QNS_EVENT_WFC_ROAMING_MODE_TO_WIFI_PREFERRED:
onWfcModeChanged(QnsConstants.WIFI_PREF, QnsConstants.COVERAGE_ROAM);
break;
case QnsEventDispatcher.QNS_EVENT_APM_ENABLED:
case QnsEventDispatcher.QNS_EVENT_WFC_DISABLED:
case QnsEventDispatcher.QNS_EVENT_WIFI_DISABLING:
if (mFallbackCounterOnDataConnectionFail > 0) {
Log.d(mLogTag, "Reset Fallback Counter on APM On/WFC off/Wifi Off");
mFallbackCounterOnDataConnectionFail = 0;
}
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS
&& hasRestrictionType(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL)) {
releaseRestriction(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL);
}
break;
default:
break;
}
}
}
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;
long mReleaseTime;
Restriction(int type, int[] releaseEvents, int restrictTime) {
mRestrictType = type;
if (restrictTime == 0) {
mReleaseTime = 0;
} else {
mReleaseTime = restrictTime + SystemClock.elapsedRealtime();
}
if (releaseEvents != null && releaseEvents.length > 0) {
mReleaseEventList = new ArrayList<>();
for (int i : releaseEvents) {
mReleaseEventList.add(i);
}
} else {
mReleaseEventList = null;
}
}
boolean needRelease(int event) {
if (mReleaseEventList == null) {
return false;
}
for (Integer i : mReleaseEventList) {
if (event == i.intValue()) {
return true;
}
}
return false;
}
void updateRestrictTime(int timeMillis) {
mReleaseTime = SystemClock.elapsedRealtime() + timeMillis;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[RESTRICTION type:").append(restrictTypeToString(mRestrictType));
builder.append(" releaseEvents:( ");
if (mReleaseEventList != null) {
for (Integer i : mReleaseEventList) {
builder.append(i).append(" ");
}
}
builder.append(") remainedTimeMillis:");
if (mReleaseTime == 0) {
builder.append("N/A");
} else {
long remain = mReleaseTime - SystemClock.elapsedRealtime();
builder.append(remain);
}
builder.append("]");
return builder.toString();
}
}
class RestrictInfo {
private int mTransportMode; // AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
private HashMap<Integer, Restriction> mRestrictionMap = new HashMap<>();
RestrictInfo(int transportMode) {
mTransportMode = transportMode;
}
HashMap<Integer, Restriction> getRestrictionMap() {
return mRestrictionMap;
}
boolean isRestricted() {
return mRestrictionMap.size() != 0;
}
/**
* This method returns if the restriction info has given restriction type.
*
* @param restrictType integer value of restriction type.
* @return true if restrictinfo has the restriction; otherwise false.
*/
boolean hasRestrictionType(@RestrictType int restrictType) {
return mRestrictionMap.get(restrictType) != null;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("RestrictInfo[")
.append(QnsConstants.transportTypeToString(mTransportMode))
.append("] : ");
if (isRestricted()) {
for (Restriction restriction : mRestrictionMap.values()) {
builder.append(restriction.toString()).append(" ");
}
} else {
builder.append("No restriction");
}
return builder.toString();
}
}
RestrictManager(
QnsComponents qnsComponents,
Looper loop,
int netCapability,
DataConnectionStatusTracker dcst,
int slotId) {
mRestrictInfos.put(
AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
new RestrictInfo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
mRestrictInfos.put(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
new RestrictInfo(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
mSlotId = slotId;
mLogTag =
RestrictManager.class.getSimpleName()
+ "_"
+ mSlotId
+ "_"
+ QnsUtils.getNameOfNetCapability(netCapability);
mTelephonyListener = qnsComponents.getQnsTelephonyListener(mSlotId);
mQnsEventDispatcher = qnsComponents.getQnsEventDispatcher(mSlotId);
mQnsCarrierConfigManager = qnsComponents.getQnsCarrierConfigManager(mSlotId);
mHandler = new RestrictManagerHandler(loop);
mNetCapability = netCapability;
mDataConnectionStatusTracker = dcst;
mQnsCallStatusTracker = qnsComponents.getQnsCallStatusTracker(mSlotId);
mActiveCallTracker = qnsComponents.getQnsCallStatusTracker(mSlotId).getActiveCallTracker();
mDataConnectionStatusTracker.registerDataConnectionStatusChanged(
mHandler, EVENT_DATA_CONNECTION_CHANGED);
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
mTelephonyListener.registerCallStateListener(
mHandler, EVENT_CALL_STATE_CHANGED, null, true);
mTelephonyListener.registerSrvccStateListener(
mHandler, EVENT_SRVCC_STATE_CHANGED, null);
}
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
mQnsImsManager = qnsComponents.getQnsImsManager(mSlotId);
mQnsImsManager.registerImsRegistrationStatusChanged(
mHandler, EVENT_IMS_REGISTRATION_STATE_CHANGED);
mWifiBackhaulMonitor = qnsComponents.getWifiBackhaulMonitor(mSlotId);
}
// check if we can pass "mQnsImsManager"
mWfcPreference = QnsUtils.getWfcMode(qnsComponents.getQnsImsManager(mSlotId), false);
mWfcRoamingPreference = QnsUtils.getWfcMode(qnsComponents.getQnsImsManager(mSlotId), true);
List<Integer> events = new ArrayList<>();
events.add(QnsEventDispatcher.QNS_EVENT_WFC_MODE_TO_WIFI_ONLY);
events.add(QnsEventDispatcher.QNS_EVENT_WFC_MODE_TO_CELLULAR_PREFERRED);
events.add(QnsEventDispatcher.QNS_EVENT_WFC_MODE_TO_WIFI_PREFERRED);
events.add(QnsEventDispatcher.QNS_EVENT_WFC_ROAMING_MODE_TO_WIFI_ONLY);
events.add(QnsEventDispatcher.QNS_EVENT_WFC_ROAMING_MODE_TO_CELLULAR_PREFERRED);
events.add(QnsEventDispatcher.QNS_EVENT_WFC_ROAMING_MODE_TO_WIFI_PREFERRED);
events.add(QnsEventDispatcher.QNS_EVENT_APM_ENABLED);
events.add(QnsEventDispatcher.QNS_EVENT_WFC_DISABLED);
events.add(QnsEventDispatcher.QNS_EVENT_WIFI_DISABLING);
mQnsEventDispatcher.registerEvent(events, mHandler);
mCellularNetworkStatusTracker = qnsComponents.getCellularNetworkStatusTracker(mSlotId);
restrictNonPreferredTransport();
}
void clearRestrictions() {
mRestrictInfos.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).getRestrictionMap().clear();
mRestrictInfos.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).getRestrictionMap().clear();
}
void close() {
mDataConnectionStatusTracker.unRegisterDataConnectionStatusChanged(mHandler);
if (mIsRttStatusCheckRegistered
&& mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
mIsRttStatusCheckRegistered = false;
mWifiBackhaulMonitor.unRegisterForRttStatusChange(mHandler);
}
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
mTelephonyListener.unregisterCallStateChanged(mHandler);
mTelephonyListener.unregisterSrvccStateChanged(mHandler);
}
mQnsEventDispatcher.unregisterEvent(mHandler);
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS
|| mNetCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
if (mActiveCallTracker != null) {
mActiveCallTracker.unregisterLowMediaQualityListener(mHandler);
}
}
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
mQnsImsManager.unregisterImsRegistrationStatusChanged(mHandler);
}
}
private void onWfcModeChanged(int prefMode, @QnsConstants.CellularCoverage int coverage) {
Log.d(mLogTag, "onWfcModeChanged prefMode :" + prefMode + " coverage:" + coverage);
if (coverage == QnsConstants.COVERAGE_HOME) {
mWfcPreference = prefMode;
} else if (coverage == QnsConstants.COVERAGE_ROAM) {
mWfcRoamingPreference = prefMode;
}
if (mCellularCoverage == coverage) {
if (prefMode == QnsConstants.CELL_PREF) {
processReleaseEvent(
AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
RELEASE_EVENT_WFC_PREFER_MODE_CHANGED);
}
if (prefMode == QnsConstants.WIFI_PREF || prefMode == QnsConstants.WIFI_ONLY) {
processReleaseEvent(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RELEASE_EVENT_WFC_PREFER_MODE_CHANGED);
}
}
checkIfCancelNonPreferredRestriction(getPreferredTransportType());
}
@VisibleForTesting
void restrictNonPreferredTransport() {
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS
&& !mCellularNetworkStatusTracker.isAirplaneModeEnabled()) {
Log.d(mLogTag, "Restrict non-preferred transport at power up");
int transportType = getPreferredTransportType();
int waitingTimer =
mQnsCarrierConfigManager.getWaitingTimerForPreferredTransportOnPowerOn(
transportType);
if (waitingTimer != QnsConstants.KEY_DEFAULT_VALUE) {
int preventTransportType = QnsUtils.getOtherTransportType(transportType);
Log.d(
mLogTag,
"prevent "
+ QnsConstants.transportTypeToString(preventTransportType)
+ " "
+ waitingTimer
+ " milli seconds");
addRestriction(
preventTransportType,
RESTRICT_TYPE_NON_PREFERRED_TRANSPORT,
sReleaseEventMap.get(RESTRICT_TYPE_NON_PREFERRED_TRANSPORT),
waitingTimer);
}
}
}
private void checkIfCancelNonPreferredRestriction(int transportType) {
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
releaseRestriction(transportType, RESTRICT_TYPE_NON_PREFERRED_TRANSPORT);
}
}
private int getPreferredTransportType() {
int transportType;
int preference = mWfcPreference;
if (mCellularCoverage == QnsConstants.COVERAGE_ROAM) {
preference = mWfcRoamingPreference;
}
if (preference == QnsConstants.WIFI_PREF || preference == QnsConstants.WIFI_ONLY) {
transportType = AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
} else {
transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
}
return transportType;
}
private void onCallStateChanged(int callState, int transportType, int cellularAn) {
Log.d(
mLogTag,
"onCallStateChanged :"
+ callState
+ " transport:"
+ transportType
+ " cellularAN:"
+ cellularAn);
mCallState = callState;
if (callState != TelephonyManager.CALL_STATE_IDLE) {
if (transportType != AccessNetworkConstants.TRANSPORT_TYPE_WLAN
&& cellularAn != AccessNetworkConstants.AccessNetworkType.EUTRAN
&& cellularAn != AccessNetworkConstants.AccessNetworkType.NGRAN) {
onCsCallStarted();
}
} else {
releaseRestriction(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL);
}
}
private void onSrvccStateChanged(int srvccState) {
Log.d(mLogTag, "onSrvccStateChanged :" + srvccState);
if (mImsCallType != QnsConstants.CALL_TYPE_IDLE
&& srvccState == TelephonyManager.SRVCC_STATE_HANDOVER_STARTED) {
addRestriction(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL,
sReleaseEventMap.get(RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL),
0);
} else if (mCallState == TelephonyManager.CALL_STATE_IDLE
|| srvccState == TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED
|| srvccState == TelephonyManager.SRVCC_STATE_HANDOVER_FAILED) {
releaseRestriction(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL);
}
}
private void onCsCallStarted() {
if (!mQnsCarrierConfigManager.allowImsOverIwlanCellularLimitedCase()) {
addRestriction(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL,
sReleaseEventMap.get(RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL),
0);
}
}
@VisibleForTesting
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_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) {
releaseRestriction(QnsUtils.getOtherTransportType(mTransportType),
RESTRICT_TYPE_GUARDING, true);
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);
}
}
}
}
@VisibleForTesting
void onDataConnectionChanged(DataConnectionChangedInfo status) {
int dataConnectionState = status.getState();
if (dataConnectionState == STATE_CONNECTED || dataConnectionState == STATE_HANDOVER) {
mTransportType = status.getTransportType();
} else {
mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
}
Log.d(mLogTag, "onDataConnectionChanged transportType:" + status);
switch (status.getEvent()) {
case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_DISCONNECTED:
processDataConnectionDisconnected();
break;
case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_STARTED:
processDataConnectionStarted(status.getTransportType());
break;
case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_CONNECTED:
processDataConnectionConnected(mTransportType);
break;
case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED:
processDataConnectionHandoverStarted();
break;
case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS:
processDataConnectionHandoverSuccess();
break;
case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_FAILED:
processDataConnectionHandoverFailed(mTransportType);
break;
case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_FAILED:
processDataConnectionFailed(status.getTransportType());
break;
default:
Log.d(mLogTag, "unknown DataConnectionChangedEvent:");
break;
}
}
private void processDataConnectionConnected(int transportType) {
// Since HO hysterisis Guard timer is expected
checkToCancelInitialPdnConnectionFailFallback();
clearInitialPdnConnectionFailFallbackRestriction();
checkIfCancelNonPreferredRestriction(QnsUtils.getOtherTransportType(transportType));
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
if (mLastEvaluatedTransportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID
|| transportType == mLastEvaluatedTransportType) {
processHandoverGuardingOperation(transportType);
} else {
Log.d(
mLogTag,
"DataConnectionConnected, but transport type is different,"
+ " Handover init may follow");
}
}
}
private void clearInitialPdnConnectionFailFallbackRestriction() {
mFallbackCounterOnDataConnectionFail = 0;
if (hasRestrictionType(
AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL)) {
releaseRestriction(
AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL);
}
if (hasRestrictionType(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL)) {
releaseRestriction(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL);
}
}
private void checkToCancelInitialPdnConnectionFailFallback() {
Log.d(mLogTag, "clear Initial PDN Connection fail Timer checks");
mIsTimerRunningOnDataConnectionFail = false;
mRetryCounterOnDataConnectionFail = 0;
if (mHandler.hasMessages(EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED)) {
mHandler.removeMessages(EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED);
}
}
private void processDataConnectionDisconnected() {
processReleaseEvent(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, RELEASE_EVENT_DISCONNECT);
processReleaseEvent(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, RELEASE_EVENT_DISCONNECT);
mCounterForIwlanRestrictionInCall = 0;
if (mDeferredThrottlingEvent != null) {
long delayMillis =
mDeferredThrottlingEvent.second - QnsUtils.getSystemElapsedRealTime();
if (delayMillis > 0) {
if (mDebugFlag) Log.d(mLogTag, "onDisconnected, process deferred Throttling event");
addRestriction(
mDeferredThrottlingEvent.first,
RESTRICT_TYPE_THROTTLING,
sReleaseEventMap.get(RESTRICT_TYPE_THROTTLING),
(int) delayMillis);
}
mDeferredThrottlingEvent = null;
}
}
private void processDataConnectionStarted(int currTransportType) {
if (mLastDataConnectionTransportType != currTransportType) {
Log.d(
mLogTag,
"clear Initial PDN Connection fallback checks for last transport type:"
+ mLastDataConnectionTransportType);
checkToCancelInitialPdnConnectionFailFallback();
if (hasRestrictionType(
mLastDataConnectionTransportType,
RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL)) {
Log.d(
mLogTag,
"PreIncrement_Fallback Counter : " + mFallbackCounterOnDataConnectionFail);
mFallbackCounterOnDataConnectionFail += 1;
}
mLastDataConnectionTransportType = currTransportType;
}
}
private void processDataConnectionHandoverStarted() {
if ((mTransportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID)
&& !hasRestrictionType(mTransportType, RestrictManager.RESTRICT_TYPE_GUARDING)) {
startGuarding(GUARDING_TIMER_HANDOVER_INIT, mTransportType);
}
}
private void processDataConnectionHandoverSuccess() {
// Handover Guarding Timer operation
processHandoverGuardingOperation(mTransportType);
// update LowRtpQualityListener
if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
|| mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
// Return to the transport type restricted by low RTP. It may be singleRAT case, release
// the restriction.
releaseRestriction(mTransportType, RESTRICT_TYPE_RTP_LOW_QUALITY);
}
}
private void processDataConnectionHandoverFailed(int transportType) {
cancelGuarding(transportType);
}
private void processHandoverGuardingOperation(int transportType) {
int guardingTransport = QnsUtils.getOtherTransportType(transportType);
int delayMillis = getGuardingTimeMillis(guardingTransport, mImsCallType);
int minimumGuardingTimer = mQnsCarrierConfigManager.getMinimumHandoverGuardingTimer();
if (delayMillis == 0 && minimumGuardingTimer > 0) {
delayMillis = minimumGuardingTimer;
}
if (delayMillis > 0) {
startGuarding(delayMillis, guardingTransport);
} else {
cancelGuarding(guardingTransport);
}
}
private void processDataConnectionFailed(int dataConnectionTransportType) {
if (mCellularNetworkStatusTracker != null
&& !mCellularNetworkStatusTracker.isAirplaneModeEnabled()) {
Log.d(mLogTag, "Initiate data connection fail Fallback support check");
checkFallbackOnDataConnectionFail(dataConnectionTransportType);
} else {
checkToCancelInitialPdnConnectionFailFallback();
}
}
private void checkFallbackOnDataConnectionFail(int transportType) {
int[] fallbackConfigOnInitDataFail =
mQnsCarrierConfigManager.getInitialDataConnectionFallbackConfig(mNetCapability);
Log.d(
mLogTag,
"FallbackConfig set is :"
+ fallbackConfigOnInitDataFail[0]
+ ":"
+ fallbackConfigOnInitDataFail[1]
+ ":"
+ fallbackConfigOnInitDataFail[2]);
if ((fallbackConfigOnInitDataFail != null && fallbackConfigOnInitDataFail[0] == 1)
&& !hasRestrictionType(
transportType, RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL)
&& (fallbackConfigOnInitDataFail[3] == 0
|| mFallbackCounterOnDataConnectionFail
< fallbackConfigOnInitDataFail[3])) {
Log.d(
mLogTag,
"FallbackCount: "
+ fallbackConfigOnInitDataFail[3]
+ "_"
+ mFallbackCounterOnDataConnectionFail);
enableFallbackRetryCountCheckOnInitialPdnFail(
transportType, fallbackConfigOnInitDataFail[1]);
enableFallbackRetryTimerCheckOnInitialPdnFail(
transportType, fallbackConfigOnInitDataFail[2]);
}
}
private void enableFallbackRetryTimerCheckOnInitialPdnFail(
int transportType, int fallbackRetryTimer) {
Log.d(
mLogTag,
"Start Initial Data Connection fail retry_timer On TransportType"
+ fallbackRetryTimer
+ "_"
+ QnsConstants.transportTypeToString(transportType));
if (fallbackRetryTimer > 0 && !mIsTimerRunningOnDataConnectionFail) {
Message msg =
mHandler.obtainMessage(
EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED,
transportType,
0,
null);
mHandler.sendMessageDelayed(msg, (long) fallbackRetryTimer);
mIsTimerRunningOnDataConnectionFail = true;
}
}
private void enableFallbackRetryCountCheckOnInitialPdnFail(
int transportType, int fallbackRetryCount) {
Log.d(
mLogTag,
"Start Initial Data Connection fail retry_count On TransportType"
+ fallbackRetryCount
+ "_"
+ mRetryCounterOnDataConnectionFail
+ "_"
+ QnsConstants.transportTypeToString(transportType));
if (fallbackRetryCount > 0) {
if (mRetryCounterOnDataConnectionFail == fallbackRetryCount) {
fallbackToOtherTransportOnDataConnectionFail(transportType);
} else {
mRetryCounterOnDataConnectionFail += 1;
}
}
}
private void fallbackToOtherTransportOnDataConnectionFail(int currTransportType) {
checkToCancelInitialPdnConnectionFailFallback();
addRestriction(
currTransportType,
RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL,
sReleaseEventMap.get(RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL),
mQnsCarrierConfigManager.getFallbackGuardTimerOnInitialConnectionFail(
mNetCapability));
}
@VisibleForTesting
void onImsRegistrationStateChanged(QnsImsManager.ImsRegistrationState event) {
Log.d(
mLogTag,
"onImsRegistrationStateChanged["
+ QnsConstants.transportTypeToString(mTransportType)
+ "] transportType["
+ QnsConstants.transportTypeToString(event.getTransportType())
+ "] RegistrationState["
+ QnsConstants.imsRegistrationEventToString(event.getEvent())
+ "]");
int prefMode =
mCellularCoverage == QnsConstants.COVERAGE_HOME
? mWfcPreference
: mWfcRoamingPreference;
registerRttStatusCheckEvent();
switch (event.getEvent()) {
case QnsConstants.IMS_REGISTRATION_CHANGED_UNREGISTERED:
onImsUnregistered(event, mTransportType, prefMode);
break;
case QnsConstants.IMS_REGISTRATION_CHANGED_ACCESS_NETWORK_CHANGE_FAILED:
onImsHoRegisterFailed(event, mTransportType, prefMode);
break;
case QnsConstants.IMS_REGISTRATION_CHANGED_REGISTERED:
Log.d(
mLogTag,
"On Ims Registered: "
+ QnsConstants.transportTypeToString(event.getTransportType()));
if (event.getTransportType() == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
&& hasRestrictionType(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL)) {
releaseRestriction(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL);
}
break;
default:
break;
}
}
private void registerRttStatusCheckEvent() {
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
if (mWifiBackhaulMonitor.isRttCheckEnabled()) {
if (!mIsRttStatusCheckRegistered) {
mIsRttStatusCheckRegistered = true;
mWifiBackhaulMonitor.registerForRttStatusChange(
mHandler, EVENT_WIFI_RTT_BACKHAUL_CHECK_STATUS);
}
} else {
if (mIsRttStatusCheckRegistered) {
mIsRttStatusCheckRegistered = false;
mWifiBackhaulMonitor.unRegisterForRttStatusChange(mHandler);
}
}
}
}
private void onImsUnregistered(
QnsImsManager.ImsRegistrationState event, int transportType, int prefMode) {
if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
int fallbackTimeMillis =
mQnsCarrierConfigManager.getFallbackTimeImsUnregistered(
event.getReasonInfo().getCode(), prefMode);
if (fallbackTimeMillis > 0
&& mQnsCarrierConfigManager.isAccessNetworkAllowed(
mCellularAccessNetwork, NetworkCapabilities.NET_CAPABILITY_IMS)) {
fallbackToWwanForImsRegistration(fallbackTimeMillis);
}
}
}
private void onImsHoRegisterFailed(
QnsImsManager.ImsRegistrationState event, int transportType, int prefMode) {
if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
&& transportType == event.getTransportType()) {
int fallbackTimeMillis =
mQnsCarrierConfigManager.getFallbackTimeImsHoRegisterFailed(
event.getReasonInfo().getCode(), prefMode);
if (fallbackTimeMillis > 0
&& mQnsCarrierConfigManager.isAccessNetworkAllowed(
mCellularAccessNetwork, NetworkCapabilities.NET_CAPABILITY_IMS)) {
fallbackToWwanForImsRegistration(fallbackTimeMillis);
}
}
}
protected void onWlanRttFail() {
Log.d(mLogTag, "start RTT Fallback:");
int fallbackTimeMillis = mQnsCarrierConfigManager.getWlanRttFallbackHystTimer();
if (fallbackTimeMillis > 0
&& mQnsCarrierConfigManager.isAccessNetworkAllowed(
mCellularAccessNetwork, NetworkCapabilities.NET_CAPABILITY_IMS)) {
fallbackToWwanForImsRegistration(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL,
fallbackTimeMillis);
}
}
private void fallbackToWwanForImsRegistration(int fallbackTimeMillis) {
fallbackToWwanForImsRegistration(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL,
fallbackTimeMillis);
}
private void fallbackToWwanForImsRegistration(
int transportType, int restrictType, int fallbackTimeMillis) {
Log.d(mLogTag, "release ignorable restrictions on WWAN to fallback.");
for (int restriction : ignorableRestrictionsOnSingleRat) {
releaseRestriction(QnsUtils.getOtherTransportType(transportType), restriction, false);
}
addRestriction(
transportType,
restrictType,
sReleaseEventMap.get(restrictType),
fallbackTimeMillis);
}
/** Update Last notified transport type from ANE which owns this RestrictManager */
void updateLastNotifiedTransportType(@AccessNetworkConstants.TransportType int transportType) {
if (mDebugFlag) {
Log.d(
mLogTag,
"updateLastEvaluatedTransportType: "
+ QnsConstants.transportTypeToString(transportType));
}
mLastEvaluatedTransportType = transportType;
if (mDataConnectionStatusTracker.isActiveState() && mTransportType != transportType) {
startGuarding(GUARDING_TIMER_HANDOVER_INIT,
QnsUtils.getOtherTransportType(transportType));
}
}
@VisibleForTesting
void setCellularCoverage(@QnsConstants.CellularCoverage int coverage) {
Log.d(mLogTag, "setCellularCoverage:" + QnsConstants.coverageToString(coverage));
mCellularCoverage = coverage;
checkIfCancelNonPreferredRestriction(getPreferredTransportType());
}
protected void setQnsCallType(@QnsConstants.QnsCallType int callType) {
if (callType != mImsCallType) {
updateGuardingTimerConditionOnCallState(mImsCallType, callType);
}
mImsCallType = callType;
Log.d(mLogTag, "setQnsCallType: " + QnsConstants.callTypeToString(callType));
if (callType == QnsConstants.CALL_TYPE_IDLE) {
Log.d(mLogTag, "Call end. init mCounterForIwlanRestrictionInCall");
mCounterForIwlanRestrictionInCall = 0;
processReleaseEvent(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, RELEASE_EVENT_CALL_END);
processReleaseEvent(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, RELEASE_EVENT_CALL_END);
unregisterLowRtpQualityEvent();
} else {
registerLowRtpQualityEvent();
}
}
private void updateGuardingTimerConditionOnCallState(int prevCallType, int newCallType) {
int currGuardingTransport = QnsUtils.getOtherTransportType(mTransportType);
if (mRestrictInfos.get(currGuardingTransport) == null) return;
HashMap<Integer, Restriction> restrictionMap =
mRestrictInfos.get(currGuardingTransport).getRestrictionMap();
Restriction restriction = restrictionMap.get(RESTRICT_TYPE_GUARDING);
if (restriction != null) {
int prevCallTypeMillis = getGuardingTimeMillis(currGuardingTransport, prevCallType);
if (prevCallTypeMillis == 0) {
return; // We don't need to update minimum guarding timer.
}
int newCallTypeMillis =
getGuardingTimeMillis(
currGuardingTransport, newCallType); // new Call type timer
if (newCallTypeMillis == prevCallTypeMillis) return;
if (newCallTypeMillis != 0) {
// remaining time on current call type
long prevCallTypeRemainingMillis =
restriction.mReleaseTime - SystemClock.elapsedRealtime();
int guardTimerElapsed = prevCallTypeMillis - (int) prevCallTypeRemainingMillis;
int newGuardTimer = newCallTypeMillis - guardTimerElapsed;
if (mDebugFlag) {
Log.d(
mLogTag,
"Prev Call Type Guarding millis:"
+ prevCallTypeMillis
+ "Prev Call type remaining millis:"
+ prevCallTypeRemainingMillis
+ "New Call type Guarding millis:"
+ newCallTypeMillis
+ "Guard timer Elapsed:"
+ guardTimerElapsed
+ "New Guard timer to set:"
+ newGuardTimer);
}
if (newGuardTimer > 0) {
startGuarding(newGuardTimer, currGuardingTransport);
return;
}
}
cancelGuarding(currGuardingTransport);
}
}
@VisibleForTesting
void setCellularAccessNetwork(int accessNetwork) {
mCellularAccessNetwork = accessNetwork;
Log.d(mLogTag, "Current Cellular Network:" + mCellularAccessNetwork);
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS
&& !mQnsCarrierConfigManager.isAccessNetworkAllowed(
accessNetwork, mNetCapability)) {
processReleaseEvent(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN, RELEASE_EVENT_IMS_NOT_SUPPORT_RAT);
}
}
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 =
mRestrictInfos.get(transport).getRestrictionMap();
Restriction restriction = restrictionMap.get(type);
Log.d(
mLogTag,
"addRestriction["
+ QnsConstants.transportTypeToString(transport)
+ "] "
+ restrictTypeToString(type)
+ " was restrict:"
+ (restriction != null));
if (restriction == null) {
restriction = new Restriction(type, releaseEvents, timeMillis);
restrictionMap.put(type, 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 releaseRestriction(int transport, int type) {
releaseRestriction(transport, type, false);
}
void releaseRestriction(int transport, int type, boolean skipNotify) {
boolean needNotify = false;
HashMap<Integer, Restriction> restrictionMap =
mRestrictInfos.get(transport).getRestrictionMap();
Restriction restriction = restrictionMap.get(type);
Log.d(
mLogTag,
"releaseRestriction["
+ QnsConstants.transportTypeToString(transport)
+ "] "
+ restrictTypeToString(type)
+ " was restrict:"
+ (restriction != null));
if (restriction == null) {
Log.d(mLogTag, "no restriction to release " + restrictTypeToString(type) + " " + type);
} else {
if (restriction.mReleaseTime > 0) {
removeReleaseRestrictionMessage(restriction);
}
restrictionMap.remove(restriction.mRestrictType);
needNotify = true;
}
if (needNotify && !skipNotify) {
notifyRestrictInfoChanged();
}
}
void processReleaseEvent(int transportType, int event) {
ArrayList<Integer> releaseList = new ArrayList<>();
HashMap<Integer, Restriction> restrictMap =
mRestrictInfos.get(transportType).getRestrictionMap();
Log.d(
mLogTag,
"processReleaseEvent["
+ QnsConstants.transportTypeToString(transportType)
+ "] "
+ event);
for (Integer restrictType : restrictMap.keySet()) {
if (restrictMap.get(restrictType).needRelease(event)) {
releaseList.add(restrictType);
}
}
for (Integer restrictType : releaseList) {
releaseRestriction(transportType, restrictType);
}
}
private void sendReleaseRestrictionMessage(int transportType, Restriction restriction) {
if (restriction == null) {
Log.e(mLogTag, "sendReleaseRestrictionMessage restriction is null");
return;
}
Message msg =
mHandler.obtainMessage(EVENT_RELEASE_RESTRICTION, transportType, 0, restriction);
long delayInMillis = restriction.mReleaseTime - SystemClock.elapsedRealtime();
mHandler.sendMessageDelayed(msg, delayInMillis);
Log.d(
mLogTag,
restrictTypeToString(restriction.mRestrictType)
+ " will be released after "
+ delayInMillis
+ " millisecs");
}
private void removeReleaseRestrictionMessage(Restriction restriction) {
if (restriction == null) {
Log.e(mLogTag, "removeReleaseRestrictionMessage restriction is null");
return;
}
mHandler.removeMessages(EVENT_RELEASE_RESTRICTION, restriction);
}
void registerRestrictInfoChanged(Handler h, int what) {
mRestrictInfoRegistrant = new QnsRegistrant(h, what, null);
}
void unRegisterRestrictInfoChanged(Handler h) {
mRestrictInfoRegistrant = null;
}
@VisibleForTesting
boolean isRestricted(int transportType) {
if (mRestrictInfos.isEmpty()) return false;
if (mRestrictInfos.get(transportType) != null) {
return mRestrictInfos.get(transportType).isRestricted();
}
return false;
}
boolean isRestrictedExceptGuarding(int transportType) {
try {
RestrictInfo info = mRestrictInfos.get(transportType);
int size = info.getRestrictionMap().size();
if (info.hasRestrictionType(RESTRICT_TYPE_GUARDING)) {
size--;
}
return size > 0;
} catch (Exception e) {
}
return false;
}
@VisibleForTesting
boolean hasRestrictionType(int transportType, int restrictType) {
try {
if (mRestrictInfos != null) {
return mRestrictInfos.get(transportType).hasRestrictionType(restrictType);
}
} catch (Exception e) {
}
return false;
}
/** This method is only for Testing */
@VisibleForTesting
protected long getRemainingGuardTimer(int transportType) {
return mRestrictInfos
.get(transportType)
.getRestrictionMap()
.get(RESTRICT_TYPE_GUARDING)
.mReleaseTime
- SystemClock.elapsedRealtime();
}
@VisibleForTesting
boolean isAllowedOnSingleTransport(int transportType) {
if (mRestrictInfos.isEmpty()) return false;
Log.d(
mLogTag,
"isAllowedOnSingleTransport ("
+ QnsConstants.transportTypeToString(transportType)
+ ") restriction :"
+ mRestrictInfos.get(transportType).toString());
int countIgnorableRestriction = 0;
for (int restrictType : ignorableRestrictionsOnSingleRat) {
if (mRestrictInfos.get(transportType).hasRestrictionType(restrictType)) {
countIgnorableRestriction++;
}
}
if (mRestrictInfos.get(transportType).getRestrictionMap().size()
== countIgnorableRestriction) {
return true;
}
return false;
}
void increaseCounterToRestrictIwlanInCall() {
mCounterForIwlanRestrictionInCall += 1;
int maxAllowedRoveOutByLowRtpQuality =
mQnsCarrierConfigManager.getQnsMaxIwlanHoCountDuringCall();
if (maxAllowedRoveOutByLowRtpQuality > 0
&& mCounterForIwlanRestrictionInCall == maxAllowedRoveOutByLowRtpQuality) {
Log.d(mLogTag, "reached maxAllowedRoveOutByLowRtpQuality");
addRestriction(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
RESTRICT_TYPE_RESTRICT_IWLAN_IN_CALL,
sReleaseEventMap.get(RESTRICT_TYPE_RESTRICT_IWLAN_IN_CALL),
0);
}
}
private void notifyRestrictInfoChanged() {
Log.d(mLogTag, "notifyRestrictInfoChanged");
if (mRestrictInfoRegistrant != null) {
mRestrictInfoRegistrant.notifyResult(mRestrictInfos);
} else {
Log.d(mLogTag, "notifyRestrictInfoChanged. no Registrant.");
}
}
private void registerLowRtpQualityEvent() {
if ((mImsCallType == QnsConstants.CALL_TYPE_VOICE
|| mImsCallType == QnsConstants.CALL_TYPE_EMERGENCY)
&& (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
|| mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
&& mActiveCallTracker != null) {
int hoRestrictTimeOnLowRtpQuality =
mQnsCarrierConfigManager.getHoRestrictedTimeOnLowRTPQuality(mTransportType);
if (hoRestrictTimeOnLowRtpQuality > 0) {
Log.d(mLogTag, "registerLowRtpQualityEvent");
mActiveCallTracker.registerLowMediaQualityListener(
mHandler, EVENT_LOW_RTP_QUALITY_REPORTED, null);
}
}
}
private void unregisterLowRtpQualityEvent() {
if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS
|| mNetCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
if (mActiveCallTracker != null) {
mActiveCallTracker.unregisterLowMediaQualityListener(mHandler);
}
}
}
private int getGuardingTimeMillis(int transportType, int callType) {
int delayMillis;
switch (mNetCapability) {
case NetworkCapabilities.NET_CAPABILITY_IMS:
case NetworkCapabilities.NET_CAPABILITY_EIMS:
if (!mQnsCarrierConfigManager.isHysteresisTimerEnabled(mCellularCoverage)) {
Log.d(
mLogTag,
"getGuardingTimeMillis: handover guarding timer is not enabled at "
+ QnsConstants.coverageToString(mCellularCoverage));
return 0;
}
if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
delayMillis =
mQnsCarrierConfigManager.getWwanHysteresisTimer(
mNetCapability, callType);
} else {
delayMillis =
mQnsCarrierConfigManager.getWlanHysteresisTimer(
mNetCapability, callType);
}
if (delayMillis > 0
&& mQnsCarrierConfigManager.isGuardTimerHysteresisOnPrefSupported()) {
int preference = mWfcPreference;
if (mCellularCoverage == QnsConstants.COVERAGE_ROAM) {
preference = mWfcRoamingPreference;
}
if (preference == QnsConstants.CELL_PREF
&& transportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
Log.d(
mLogTag,
"getGuardingTimeMillis: cellular preferred case, don't guard"
+ " handover to WLAN");
delayMillis = 0;
} else if (preference == QnsConstants.WIFI_PREF
&& transportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
Log.d(
mLogTag,
"getGuardingTimeMillis: wifi preferred case, don't guard handover"
+ " to WWAN");
delayMillis = 0;
}
}
break;
case NetworkCapabilities.NET_CAPABILITY_MMS:
case NetworkCapabilities.NET_CAPABILITY_XCAP:
case NetworkCapabilities.NET_CAPABILITY_CBS:
callType = mQnsCallStatusTracker.isCallIdle() ? QnsConstants.CALL_TYPE_IDLE
: QnsConstants.CALL_TYPE_VOICE;
if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
delayMillis =
mQnsCarrierConfigManager.getWwanHysteresisTimer(
mNetCapability, callType);
} else if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
delayMillis =
mQnsCarrierConfigManager.getWlanHysteresisTimer(
mNetCapability, callType);
} else {
delayMillis = 0;
}
break;
default:
delayMillis = 0;
break;
}
Log.d(
mLogTag,
"getGuardingTimeMillis: timer = "
+ delayMillis
+ " for transport type = "
+ QnsConstants.transportTypeToString(transportType)
+ " in "
+ QnsConstants.callTypeToString(callType)
+ " state.");
return delayMillis;
}
@VisibleForTesting
void startGuarding(int delay, int transportType) {
// It is invalid to run to RESTRICT_TYPE_GUARDING for both Transport at same time
// Make sure to release source TransportType Guarding before starting guarding for New
// Transport
// Type
if (transportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID
&& 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(
QnsUtils.getOtherTransportType(transportType), RESTRICT_TYPE_GUARDING, true);
}
addRestriction(
transportType,
RESTRICT_TYPE_GUARDING,
sReleaseEventMap.get(RESTRICT_TYPE_GUARDING),
delay);
}
private void cancelGuarding(int transportType) {
releaseRestriction(transportType, RESTRICT_TYPE_GUARDING);
}
protected void notifyThrottling(boolean throttle, long throttleTime, int transportType) {
Log.d(
mLogTag,
"notifyThrottling throttle:"
+ throttle
+ " throttleTime:"
+ throttleTime
+ " transportType:"
+ QnsConstants.transportTypeToString(transportType));
if (throttle) {
long delayMillis = throttleTime - SystemClock.elapsedRealtime();
if (delayMillis > 0) {
if (mDataConnectionStatusTracker.isActiveState()) {
Log.d(
mLogTag,
"Defer Throttling event during active state transportType:"
+ transportType
+ " ThrottleTime:"
+ throttleTime);
mDeferredThrottlingEvent = new Pair<>(transportType, throttleTime);
} else {
addRestriction(
transportType,
RESTRICT_TYPE_THROTTLING,
sReleaseEventMap.get(RESTRICT_TYPE_THROTTLING),
(int) delayMillis);
}
}
} else {
releaseRestriction(transportType, RESTRICT_TYPE_THROTTLING);
if (mDeferredThrottlingEvent != null) mDeferredThrottlingEvent = null;
}
}
static String restrictTypeToString(int restrictType) {
switch (restrictType) {
case RESTRICT_TYPE_GUARDING:
return "RESTRICT_TYPE_GUARDING";
case RESTRICT_TYPE_THROTTLING:
return "RESTRICT_TYPE_THROTTLING";
case RESTRICT_TYPE_HO_NOT_ALLOWED:
return "RESTRICT_TYPE_HO_NOT_ALLOWED";
case RESTRICT_TYPE_NON_PREFERRED_TRANSPORT:
return "RESTRICT_TYPE_NON_PREFERRED_TRANSPORT";
case RESTRICT_TYPE_RTP_LOW_QUALITY:
return "RESTRICT_TYPE_RTP_LOW_QUALITY";
case RESTRICT_TYPE_RESTRICT_IWLAN_IN_CALL:
return "RESTRICT_TYPE_RESTRICT_IWLAN_IN_CALL";
case RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL:
return "RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL";
case RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL:
return "RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL";
case RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL:
return "RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL";
case RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL:
return "RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL";
}
return "";
}
/**
* Dumps the state of {@link QualityMonitor}
*
* @param pw {@link PrintWriter} to write the state of the object.
* @param prefix String to append at start of dumped log.
*/
void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "------------------------------");
pw.println(
prefix
+ "RestrictManager["
+ QnsUtils.getNameOfNetCapability(mNetCapability)
+ "_"
+ mSlotId
+ "]:");
pw.println(
prefix
+ "mTransportType="
+ QnsConstants.transportTypeToString(mTransportType)
+ ", mLastEvaluatedTransportType="
+ QnsConstants.transportTypeToString(mLastEvaluatedTransportType)
+ ", mLastDataConnectionTransportType="
+ QnsConstants.transportTypeToString(mLastDataConnectionTransportType));
pw.println(
prefix
+ "mCounterForIwlanRestrictionInCall="
+ mCounterForIwlanRestrictionInCall
+ ", mRetryCounterOnDataConnectionFail="
+ mRetryCounterOnDataConnectionFail
+ ", mFallbackCounterOnDataConnectionFail="
+ mFallbackCounterOnDataConnectionFail);
pw.println(
prefix
+ "mImsCallType="
+ QnsConstants.callTypeToString(mImsCallType)
+ ", mCallState="
+ QnsConstants.callStateToString(mCallState));
pw.println(prefix + "mRestrictInfos=" + mRestrictInfos);
}
}