blob: a1af8b523508f85cc33b30ce5b80d9a4380140a8 [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.qns;
import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ACTIVE;
import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ALERTING;
import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_DIALING;
import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED;
import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_HOLDING;
import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_IDLE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Registrant;
import android.telephony.Annotation;
import android.telephony.PreciseDataConnectionState;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
public class AlternativeEventListener {
private static final int EVENT_SRVCC_STATE_CHANGED = 13001;
private static final SparseArray<AlternativeEventListener> sAlternativeInterfaceManager =
new SparseArray<>();
private final String LOG_TAG;
private Context mContext;
private int mSlotIndex;
private Registrant mCallTypeChangedEventListener;
private Registrant mEmergencyCallTypeChangedEventListener;
private Registrant mEmergencyPreferredTransportTypeChanged;
private Registrant mTryWfcConnectionState;
private Registrant mLowRtpQuallityListener;
private Registrant mEmcLowRtpQuallityListener;
private AlternativeEventCb mEventCb;
private AlternativeEventProvider mEventProvider;
private Handler mHandler;
private CallInfoManager mCallInfoManager = new CallInfoManager();
class CallInfo {
int mId;
int mType;
int mState;
public CallInfo(int id, int type, int state) {
mId = id;
mType = type;
mState = state;
}
}
/** Manager of CallInfo list. */
private class CallInfoManager {
private SparseArray<CallInfo> callInfos = new SparseArray<>();
QnsCarrierConfigManager.RtpMetricsConfig mRtpMetricsConfig;
int mEmergencyCallState = PRECISE_CALL_STATE_IDLE;
int mLastReportedCallType = QnsConstants.CALL_TYPE_IDLE;
public CallInfoManager() {
mRtpMetricsConfig = null;
}
public void updateCallInfo(
int id,
@QnsConstants.QnsCallType int type,
@Annotation.PreciseCallStates int state) {
if (mRtpMetricsConfig != null && state == PRECISE_CALL_STATE_ACTIVE) {
requestRtpThreshold(mRtpMetricsConfig);
}
if (type == QnsConstants.CALL_TYPE_EMERGENCY) {
mEmergencyCallState = state;
return;
}
if (type == QnsConstants.CALL_TYPE_VIDEO || type == QnsConstants.CALL_TYPE_VOICE) {
CallInfo info = callInfos.get(id);
if (info == null) {
if (state == PRECISE_CALL_STATE_ACTIVE) {
callInfos.put(id, new CallInfo(id, type, state));
log("add callinfo with id " + id);
}
} else {
if (info.mType != type) {
log("CallId[" + info.mId + "] type changed " + info.mType + " > " + id);
info.mType = type;
}
if (state == PRECISE_CALL_STATE_HOLDING) {
log("CallId[" + info.mId + "] changed to HOLDING ");
info.mState = state;
} else if (state == PRECISE_CALL_STATE_ACTIVE) {
log("CallId[" + info.mId + "] changed to ACTIVE ");
info.mState = state;
} else if (state == PRECISE_CALL_STATE_DISCONNECTED) {
log("delete callInfo callId:" + id);
callInfos.remove(id);
}
}
}
}
boolean isCallIdle() {
return callInfos.size() == 0;
}
CallInfo getActiveCallInfo() {
for (int i = 0; i < callInfos.size(); i++) {
int key = callInfos.keyAt(i);
if (callInfos.get(key).mState == PRECISE_CALL_STATE_ACTIVE) {
return callInfos.get(key);
}
}
return null;
}
void clearCallInfo() {
callInfos.clear();
}
boolean hasVideoCall() {
for (int i = 0; i < callInfos.size(); i++) {
int key = callInfos.keyAt(i);
if (callInfos.get(key).mType == QnsConstants.CALL_TYPE_VIDEO) {
return true;
}
}
return false;
}
void requestRtpThreshold(QnsCarrierConfigManager.RtpMetricsConfig config) {
if (config != null) {
mRtpMetricsConfig =
new QnsCarrierConfigManager.RtpMetricsConfig(
config.mJitter,
config.mPktLossRate,
config.mPktLossTime,
config.mNoRtpInterval);
}
CallInfo info = getActiveCallInfo();
if (mEventProvider != null && info != null) {
log("requestRtpThreshold callId:" + info.mId + " " + config.toString());
mEventProvider.requestRtpThreshold(
info.mId,
mRtpMetricsConfig.mJitter,
mRtpMetricsConfig.mPktLossRate,
mRtpMetricsConfig.mPktLossTime,
mRtpMetricsConfig.mNoRtpInterval);
}
}
}
class MessageHandler extends Handler {
public MessageHandler(Looper l) {
super(l);
}
@Override
public void handleMessage(Message message) {
Log.d(LOG_TAG, "handleMessage msg=" + message.what);
AsyncResult ar = (AsyncResult) message.obj;
int state = (int) ar.result;
switch (message.what) {
case EVENT_SRVCC_STATE_CHANGED:
onSrvccStateChanged(state);
break;
default:
Log.d(LOG_TAG, "Unknown message received!");
break;
}
}
}
private AlternativeEventListener(@NonNull Context context, int slotIndex) {
LOG_TAG =
QnsConstants.QNS_TAG
+ "_"
+ AlternativeEventListener.class.getSimpleName()
+ "_"
+ slotIndex;
mContext = context;
mSlotIndex = slotIndex;
mHandler = new AlternativeEventListener.MessageHandler(mContext.getMainLooper());
mEventCb = new AlternativeEventCb();
}
/**
* getter of AlternativeEventListener instance.
*
* @param context application context
* @param slotIndex slot id
*/
public static AlternativeEventListener getInstance(@NonNull Context context, int slotIndex) {
AlternativeEventListener altIntMngr = sAlternativeInterfaceManager.get(slotIndex);
if (altIntMngr == null) {
altIntMngr = new AlternativeEventListener(context, slotIndex);
slog("getInstance create new instance slotId" + slotIndex);
sAlternativeInterfaceManager.put(slotIndex, altIntMngr);
}
return altIntMngr;
}
/**
* register emergency preferred transport type changed event.
*
* @param h Handler want to receive event.
* @param what event Id to receive
* @param userObj user object
*/
public void registerEmergencyPreferredTransportTypeChanged(
@NonNull Handler h, int what, Object userObj) {
mEmergencyPreferredTransportTypeChanged = new Registrant(h, what, userObj);
}
/** Unregister emergency preferred transport type changed event. */
public void unregisterEmergencyPreferredTransportTypeChanged() {
mEmergencyPreferredTransportTypeChanged = null;
}
/**
* register try WFC connection state change event.
*
* @param h Handler want to receive event
* @param what event Id to receive
* @param userObj user object
*/
public void registerTryWfcConnectionStateListener(
@NonNull Handler h, int what, Object userObj) {
mTryWfcConnectionState = new Registrant(h, what, userObj);
}
/**
* Register low RTP quality event.
*
* @param apnType APN type
* @param h Handler want to receive event.
* @param what event Id to receive
* @param userObj user object
*/
public void registerLowRtpQualityEvent(
int apnType,
@NonNull Handler h,
int what,
Object userObj,
QnsCarrierConfigManager.RtpMetricsConfig config) {
if (h != null) {
Registrant r = new Registrant(h, what, userObj);
if (apnType == ApnSetting.TYPE_IMS) {
mLowRtpQuallityListener = r;
} else if (apnType == ApnSetting.TYPE_EMERGENCY) {
mEmcLowRtpQuallityListener = r;
}
if (mEventProvider != null) {
mCallInfoManager.requestRtpThreshold(config);
}
}
}
/**
* Unregister low RTP quality event.
*
* @param apnType APN type
* @param h Handler want to receive event.
*/
public void unregisterLowRtpQualityEvent(int apnType, @NonNull Handler h) {
if (apnType == ApnSetting.TYPE_IMS) {
mLowRtpQuallityListener = null;
} else if (apnType == ApnSetting.TYPE_EMERGENCY) {
mEmcLowRtpQuallityListener = null;
}
mCallInfoManager.mRtpMetricsConfig = null;
}
/**
* register call type changed event.
*
* @param apnType apnType of caller
* @param h Handler want to receive event.
* @param what event Id to receive
* @param userObj user object
*/
public void registerCallTypeChangedListener(
@ApnSetting.ApnType int apnType, @NonNull Handler h, int what, Object userObj) {
if (apnType != ApnSetting.TYPE_IMS && apnType != ApnSetting.TYPE_EMERGENCY) {
log("registerCallTypeChangedListener : wrong ApnType");
return;
}
if (h != null) {
Registrant r = new Registrant(h, what, userObj);
if (apnType == ApnSetting.TYPE_IMS) {
mCallTypeChangedEventListener = r;
QnsTelephonyListener.getInstance(mContext, mSlotIndex)
.registerSrvccStateListener(mHandler, EVENT_SRVCC_STATE_CHANGED, null);
} else if (apnType == ApnSetting.TYPE_EMERGENCY) {
mEmergencyCallTypeChangedEventListener = r;
}
} else {
log("registerCallTypeChangedListener : Handler is Null");
}
}
/**
* Unregister call type changed event.
*
* @param apnType apnType of caller
* @param h Handler want to receive event.
*/
public void unregisterCallTypeChangedListener(
@ApnSetting.ApnType int apnType, @NonNull Handler h) {
if (apnType != ApnSetting.TYPE_IMS && apnType != ApnSetting.TYPE_EMERGENCY) {
log("unregisterCallTypeChangedListener : wrong ApnType");
return;
}
if (h != null) {
if (apnType == ApnSetting.TYPE_IMS) {
mCallTypeChangedEventListener = null;
QnsTelephonyListener.getInstance(mContext, mSlotIndex)
.unregisterSrvccStateChanged(mHandler);
} else if (apnType == ApnSetting.TYPE_EMERGENCY) {
mEmergencyCallTypeChangedEventListener = null;
}
} else {
log("unregisterCallTypeChangedListener : Handler is Null");
}
}
/**
* register EventProvider to EventListener
*
* @param provider extends of AlternativeEventProvider
*/
public void setEventProvider(AlternativeEventProvider provider) {
log("setEventProvider provider " + provider);
mEventProvider = provider;
provider.registerCallBack(mEventCb);
}
@VisibleForTesting
void onSrvccStateChanged(int srvccState) {
if (srvccState == TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED) {
mCallInfoManager.clearCallInfo();
int callType = QnsConstants.CALL_TYPE_IDLE;
mCallTypeChangedEventListener.notifyResult(callType);
}
}
void clearNormalCallInfo() {
mCallInfoManager.clearCallInfo();
mCallInfoManager.mLastReportedCallType = QnsConstants.CALL_TYPE_IDLE;
unregisterLowRtpQualityEvent(ApnSetting.TYPE_IMS, null);
}
@VisibleForTesting
void notifyRtpLowQuality(int callType, int reason) {
if (callType == QnsConstants.CALL_TYPE_VOICE) {
if (mLowRtpQuallityListener != null) {
mLowRtpQuallityListener.notifyResult(reason);
} else {
log("notifyRtpLowQuality mLowRtpQuallityListener is null.");
}
} else if (callType == QnsConstants.CALL_TYPE_EMERGENCY) {
if (mEmcLowRtpQuallityListener != null) {
mEmcLowRtpQuallityListener.notifyResult(reason);
} else {
log("notifyRtpLowQuality mEmcLowRtpQuallityListener is null.");
}
if (mCallInfoManager.mLastReportedCallType == QnsConstants.CALL_TYPE_EMERGENCY) {
if (mLowRtpQuallityListener != null) {
log("notifyRtpLowQuality for emergency call to IMS ANE");
mLowRtpQuallityListener.notifyResult(reason);
} else {
log("notifyRtpLowQuality mLowRtpQuallityListener is null.");
}
}
}
}
public class AlternativeEventCb implements AlternativeEventProvider.EventCallback {
@Override
public void onCallInfoChanged(
int id,
@QnsConstants.QnsCallType int type,
@Annotation.PreciseCallStates int state) {
log("onCallInfoChanged callId" + id + " type" + type + " state" + state);
mCallInfoManager.updateCallInfo(id, type, state);
if (type == QnsConstants.CALL_TYPE_EMERGENCY) {
if (mEmergencyCallTypeChangedEventListener != null) {
if (state == PRECISE_CALL_STATE_DISCONNECTED) {
mEmergencyCallTypeChangedEventListener.notifyResult(
QnsConstants.CALL_TYPE_IDLE);
} else if (state == PRECISE_CALL_STATE_ACTIVE) {
mEmergencyCallTypeChangedEventListener.notifyResult(
QnsConstants.CALL_TYPE_EMERGENCY);
}
}
if (mCallTypeChangedEventListener != null) {
if ((state == PRECISE_CALL_STATE_ACTIVE
|| state == PRECISE_CALL_STATE_DIALING
|| state == PRECISE_CALL_STATE_ALERTING)
&& !isDataNetworkConnected(ApnSetting.TYPE_EMERGENCY)
&& isDataNetworkConnected(ApnSetting.TYPE_IMS)) {
log("Emergency call is progressing without emergency PDN");
if (mCallInfoManager.mLastReportedCallType
!= QnsConstants.CALL_TYPE_EMERGENCY) {
mCallTypeChangedEventListener.notifyResult(
QnsConstants.CALL_TYPE_EMERGENCY);
mCallInfoManager.mLastReportedCallType =
QnsConstants.CALL_TYPE_EMERGENCY;
}
} else if (state == PRECISE_CALL_STATE_DISCONNECTED) {
log("Emergency call disconnected");
if (mCallInfoManager.mLastReportedCallType
== QnsConstants.CALL_TYPE_EMERGENCY) {
mCallTypeChangedEventListener.notifyResult(QnsConstants.CALL_TYPE_IDLE);
mCallInfoManager.mLastReportedCallType = QnsConstants.CALL_TYPE_IDLE;
}
}
}
return;
}
if (mCallTypeChangedEventListener != null) {
int callType = QnsConstants.CALL_TYPE_IDLE;
if (mCallInfoManager.isCallIdle()) {
callType = QnsConstants.CALL_TYPE_IDLE;
} else if (mCallInfoManager.hasVideoCall()) {
callType = QnsConstants.CALL_TYPE_VIDEO;
} else {
callType = QnsConstants.CALL_TYPE_VOICE;
}
if (mCallTypeChangedEventListener != null
&& mCallInfoManager.mLastReportedCallType != callType) {
mCallTypeChangedEventListener.notifyResult(callType);
mCallInfoManager.mLastReportedCallType = callType;
}
}
}
@Override
public void onVoiceRtpLowQuality(@QnsConstants.RtpLowQualityReason int reason) {
if (mCallInfoManager.mEmergencyCallState == PRECISE_CALL_STATE_ACTIVE) {
notifyRtpLowQuality(QnsConstants.CALL_TYPE_EMERGENCY, reason);
} else if (!mCallInfoManager.isCallIdle() && !mCallInfoManager.hasVideoCall()) {
notifyRtpLowQuality(QnsConstants.CALL_TYPE_VOICE, reason);
}
}
@Override
public void onEmergencyPreferenceChanged(int preferredTransportType) {
if (mEmergencyPreferredTransportTypeChanged != null) {
mEmergencyPreferredTransportTypeChanged.notifyResult(preferredTransportType);
}
}
@Override
public void onTryWfcConnectionStateChanged(boolean isEnabled) {
mTryWfcConnectionState.notifyResult(isEnabled);
}
}
protected boolean isIdleState() {
return mCallInfoManager.isCallIdle();
}
protected void setEcnoSignalThreshold(@Nullable int[] threshold) {
if (mEventProvider != null) {
mEventProvider.setEcnoSignalThreshold(threshold);
}
}
private boolean isDataNetworkConnected(int apnType) {
PreciseDataConnectionState preciseDataStatus =
QnsTelephonyListener.getInstance(mContext, mSlotIndex)
.getLastPreciseDataConnectionState(apnType);
if (preciseDataStatus == null) return false;
int state = preciseDataStatus.getState();
return (state == TelephonyManager.DATA_CONNECTED
|| state == TelephonyManager.DATA_HANDOVER_IN_PROGRESS
|| state == TelephonyManager.DATA_SUSPENDED);
}
@VisibleForTesting
protected void close() {
mCallTypeChangedEventListener = null;
mEmergencyCallTypeChangedEventListener = null;
mEmergencyPreferredTransportTypeChanged = null;
mTryWfcConnectionState = null;
mLowRtpQuallityListener = null;
mEmcLowRtpQuallityListener = null;
sAlternativeInterfaceManager.remove(mSlotIndex);
}
protected void log(String s) {
Log.d(LOG_TAG, s);
}
protected static void slog(String s) {
Log.d("AlternativeEventListener", s);
}
}