blob: dc34f5d2a99446ef23418ae6e0ef9a747f5e658d [file] [log] [blame]
/*
* Copyright (C) 2013 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.internal.telephony.imsphone;
import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE;
import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAIC;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAICr;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAOC;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAOIC;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAOICxH;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_ALL;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_MO;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_MT;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BIC_ACR;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ERASURE;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_REGISTRATION;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL_CONDITIONAL;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_BUSY;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_NOT_REACHABLE;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_NO_REPLY;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncResult;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Registrant;
import android.os.RegistrantList;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.sysprop.TelephonyProperties;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.UssdResponse;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallForwardInfo;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsSsData;
import android.telephony.ims.ImsSsInfo;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.stub.ImsUtImplBase;
import android.text.TextUtils;
import android.util.LocalLog;
import com.android.ims.ImsEcbm;
import com.android.ims.ImsEcbmStateListener;
import com.android.ims.ImsException;
import com.android.ims.ImsManager;
import com.android.ims.ImsUtInterface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallFailCause;
import com.android.internal.telephony.CallForwardInfo;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.CallTracker;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.GsmCdmaPhone;
import com.android.internal.telephony.MmiCode;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneNotifier;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.TelephonyComponentFactory;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.dataconnection.TransportManager;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.metrics.ImsStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.metrics.VoiceCallSessionStats;
import com.android.internal.telephony.nano.TelephonyProto.ImsConnectionState;
import com.android.internal.telephony.uicc.IccRecords;
import com.android.internal.telephony.util.NotificationChannelController;
import com.android.internal.util.IndentingPrintWriter;
import com.android.telephony.Rlog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* {@hide}
*/
public class ImsPhone extends ImsPhoneBase {
private static final String LOG_TAG = "ImsPhone";
private static final boolean DBG = true;
private static final boolean VDBG = false; // STOPSHIP if true
private static final int EVENT_SET_CALL_BARRING_DONE = EVENT_LAST + 1;
private static final int EVENT_GET_CALL_BARRING_DONE = EVENT_LAST + 2;
private static final int EVENT_SET_CALL_WAITING_DONE = EVENT_LAST + 3;
private static final int EVENT_GET_CALL_WAITING_DONE = EVENT_LAST + 4;
private static final int EVENT_SET_CLIR_DONE = EVENT_LAST + 5;
private static final int EVENT_GET_CLIR_DONE = EVENT_LAST + 6;
private static final int EVENT_DEFAULT_PHONE_DATA_STATE_CHANGED = EVENT_LAST + 7;
@VisibleForTesting
public static final int EVENT_SERVICE_STATE_CHANGED = EVENT_LAST + 8;
private static final int EVENT_VOICE_CALL_ENDED = EVENT_LAST + 9;
private static final int EVENT_INITIATE_VOLTE_SILENT_REDIAL = EVENT_LAST + 10;
private static final int EVENT_GET_CLIP_DONE = EVENT_LAST + 11;
static final int RESTART_ECM_TIMER = 0; // restart Ecm timer
static final int CANCEL_ECM_TIMER = 1; // cancel Ecm timer
// Default Emergency Callback Mode exit timer
private static final long DEFAULT_ECM_EXIT_TIMER_VALUE = 300000;
// String to Call Composer Option Prefix set by user
private static final String PREF_USER_SET_CALL_COMPOSER_PREFIX = "userset_callcomposer_prefix";
/**
* Used to create ImsManager instances, which may be injected during testing.
*/
@VisibleForTesting
public interface ImsManagerFactory {
/**
* Create a new instance of ImsManager for the specified phoneId.
*/
ImsManager create(Context context, int phoneId);
}
public static class ImsDialArgs extends DialArgs {
public static class Builder extends DialArgs.Builder<ImsDialArgs.Builder> {
private android.telecom.Connection.RttTextStream mRttTextStream;
private int mRetryCallFailCause = ImsReasonInfo.CODE_UNSPECIFIED;
private int mRetryCallFailNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
private boolean mIsWpsCall = false;
public static ImsDialArgs.Builder from(DialArgs dialArgs) {
if (dialArgs instanceof ImsDialArgs) {
return new ImsDialArgs.Builder()
.setUusInfo(dialArgs.uusInfo)
.setIsEmergency(dialArgs.isEmergency)
.setVideoState(dialArgs.videoState)
.setIntentExtras(dialArgs.intentExtras)
.setRttTextStream(((ImsDialArgs)dialArgs).rttTextStream)
.setClirMode(dialArgs.clirMode)
.setRetryCallFailCause(((ImsDialArgs)dialArgs).retryCallFailCause)
.setRetryCallFailNetworkType(
((ImsDialArgs)dialArgs).retryCallFailNetworkType)
.setIsWpsCall(((ImsDialArgs)dialArgs).isWpsCall);
}
return new ImsDialArgs.Builder()
.setUusInfo(dialArgs.uusInfo)
.setIsEmergency(dialArgs.isEmergency)
.setVideoState(dialArgs.videoState)
.setClirMode(dialArgs.clirMode)
.setIntentExtras(dialArgs.intentExtras);
}
public ImsDialArgs.Builder setRttTextStream(
android.telecom.Connection.RttTextStream s) {
mRttTextStream = s;
return this;
}
public ImsDialArgs.Builder setRetryCallFailCause(int retryCallFailCause) {
this.mRetryCallFailCause = retryCallFailCause;
return this;
}
public ImsDialArgs.Builder setRetryCallFailNetworkType(int retryCallFailNetworkType) {
this.mRetryCallFailNetworkType = retryCallFailNetworkType;
return this;
}
public ImsDialArgs.Builder setIsWpsCall(boolean isWpsCall) {
this.mIsWpsCall = isWpsCall;
return this;
}
public ImsDialArgs build() {
return new ImsDialArgs(this);
}
}
/**
* The RTT text stream. If non-null, indicates that connection supports RTT
* communication with the in-call app.
*/
public final android.telecom.Connection.RttTextStream rttTextStream;
public final int retryCallFailCause;
public final int retryCallFailNetworkType;
/** Indicates the call is Wireless Priority Service call */
public final boolean isWpsCall;
private ImsDialArgs(ImsDialArgs.Builder b) {
super(b);
this.rttTextStream = b.mRttTextStream;
this.retryCallFailCause = b.mRetryCallFailCause;
this.retryCallFailNetworkType = b.mRetryCallFailNetworkType;
this.isWpsCall = b.mIsWpsCall;
}
}
// Instance Variables
Phone mDefaultPhone;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
ImsPhoneCallTracker mCT;
ImsExternalCallTracker mExternalCallTracker;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private ArrayList <ImsPhoneMmiCode> mPendingMMIs = new ArrayList<ImsPhoneMmiCode>();
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private ServiceState mSS = new ServiceState();
private final ImsManagerFactory mImsManagerFactory;
private SharedPreferences mImsPhoneSharedPreferences;
// To redial silently through GSM or CDMA when dialing through IMS fails
private String mLastDialString;
private WakeLock mWakeLock;
// mEcmExitRespRegistrant is informed after the phone has been exited the emergency
// callback mode keep track of if phone is in emergency callback mode
private Registrant mEcmExitRespRegistrant;
private final RegistrantList mSilentRedialRegistrants = new RegistrantList();
private final LocalLog mRegLocalLog = new LocalLog(100);
private TelephonyMetrics mMetrics;
// The helper class to receive and store the MmTel registration status updated.
private ImsRegistrationCallbackHelper mImsMmTelRegistrationHelper;
// The roaming state if currently in service, or the last roaming state when was in service.
private boolean mLastKnownRoamingState = false;
private boolean mIsInImsEcm = false;
// List of Registrants to send supplementary service notifications to.
private RegistrantList mSsnRegistrants = new RegistrantList();
private ImsStats mImsStats;
// A runnable which is used to automatically exit from Ecm after a period of time.
private Runnable mExitEcmRunnable = new Runnable() {
@Override
public void run() {
exitEmergencyCallbackMode();
}
};
private Uri[] mCurrentSubscriberUris;
protected void setCurrentSubscriberUris(Uri[] currentSubscriberUris) {
this.mCurrentSubscriberUris = currentSubscriberUris;
}
@Override
public Uri[] getCurrentSubscriberUris() {
return mCurrentSubscriberUris;
}
/** Set call composer status from users for the current subscription */
public void setCallComposerStatus(int status) {
mImsPhoneSharedPreferences.edit().putInt(
PREF_USER_SET_CALL_COMPOSER_PREFIX + getSubId(), status).commit();
}
/** Get call composer status from users for the current subscription */
public int getCallComposerStatus() {
return mImsPhoneSharedPreferences.getInt(PREF_USER_SET_CALL_COMPOSER_PREFIX + getSubId(),
TelephonyManager.CALL_COMPOSER_STATUS_OFF);
}
@Override
public int getEmergencyNumberDbVersion() {
return getEmergencyNumberTracker().getEmergencyNumberDbVersion();
}
@Override
public EmergencyNumberTracker getEmergencyNumberTracker() {
return mDefaultPhone.getEmergencyNumberTracker();
}
@Override
public ServiceStateTracker getServiceStateTracker() {
return mDefaultPhone.getServiceStateTracker();
}
// Create Cf (Call forward) so that dialling number &
// mIsCfu (true if reason is call forward unconditional)
// mOnComplete (Message object passed by client) can be packed &
// given as a single Cf object as user data to UtInterface.
private static class Cf {
final String mSetCfNumber;
final Message mOnComplete;
final boolean mIsCfu;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Cf(String cfNumber, boolean isCfu, Message onComplete) {
mSetCfNumber = cfNumber;
mIsCfu = isCfu;
mOnComplete = onComplete;
}
}
// Create SS (Supplementary Service) so that save SS params &
// mOnComplete (Message object passed by client) can be packed
// given as a single SS object as user data to UtInterface.
@VisibleForTesting
public static class SS {
int mCfAction;
int mCfReason;
String mDialingNumber;
int mTimerSeconds;
boolean mEnable;
int mClirMode;
String mFacility;
boolean mLockState;
String mPassword;
int mServiceClass;
@VisibleForTesting
public Message mOnComplete;
// Default // Query CW, CLIR, CLIP
SS(Message onComplete) {
mOnComplete = onComplete;
}
// Update CLIP
SS(boolean enable, Message onComplete) {
mEnable = enable;
mOnComplete = onComplete;
}
// Update CLIR
SS(int clirMode, Message onComplete) {
mClirMode = clirMode;
mOnComplete = onComplete;
}
// Update CW
SS(boolean enable, int serviceClass, Message onComplete) {
mEnable = enable;
mServiceClass = serviceClass;
mOnComplete = onComplete;
}
// Query CF
SS(int cfReason, int serviceClass, Message onComplete) {
mCfReason = cfReason;
mServiceClass = serviceClass;
mOnComplete = onComplete;
}
// Update CF
SS(int cfAction, int cfReason, String dialingNumber,
int serviceClass, int timerSeconds, Message onComplete) {
mCfAction = cfAction;
mCfReason = cfReason;
mDialingNumber = dialingNumber;
mServiceClass = serviceClass;
mTimerSeconds = timerSeconds;
mOnComplete = onComplete;
}
// Query CB
SS(String facility, String password, int serviceClass, Message onComplete) {
mFacility = facility;
mPassword = password;
mServiceClass = serviceClass;
mOnComplete = onComplete;
}
// Update CB
SS(String facility, boolean lockState, String password,
int serviceClass, Message onComplete) {
mFacility = facility;
mLockState = lockState;
mPassword = password;
mServiceClass = serviceClass;
mOnComplete = onComplete;
}
}
// Constructors
public ImsPhone(Context context, PhoneNotifier notifier, Phone defaultPhone) {
this(context, notifier, defaultPhone, ImsManager::getInstance, false);
}
@VisibleForTesting
public ImsPhone(Context context, PhoneNotifier notifier, Phone defaultPhone,
ImsManagerFactory imsManagerFactory, boolean unitTestMode) {
super("ImsPhone", context, notifier, unitTestMode);
mDefaultPhone = defaultPhone;
mImsManagerFactory = imsManagerFactory;
mImsPhoneSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
mImsStats = new ImsStats(this);
// The ImsExternalCallTracker needs to be defined before the ImsPhoneCallTracker, as the
// ImsPhoneCallTracker uses a thread to spool up the ImsManager. Part of this involves
// setting the multiendpoint listener on the external call tracker. So we need to ensure
// the external call tracker is available first to avoid potential timing issues.
mExternalCallTracker =
TelephonyComponentFactory.getInstance()
.inject(ImsExternalCallTracker.class.getName())
.makeImsExternalCallTracker(this);
mCT = TelephonyComponentFactory.getInstance().inject(ImsPhoneCallTracker.class.getName())
.makeImsPhoneCallTracker(this);
mCT.registerPhoneStateListener(mExternalCallTracker);
mExternalCallTracker.setCallPuller(mCT);
mSS.setStateOff();
mPhoneId = mDefaultPhone.getPhoneId();
mMetrics = TelephonyMetrics.getInstance();
mImsMmTelRegistrationHelper = new ImsRegistrationCallbackHelper(mMmTelRegistrationUpdate,
context.getMainExecutor());
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
mWakeLock.setReferenceCounted(false);
if (mDefaultPhone.getServiceStateTracker() != null
&& mDefaultPhone.getTransportManager() != null) {
for (int transport : mDefaultPhone.getTransportManager().getAvailableTransports()) {
mDefaultPhone.getServiceStateTracker()
.registerForDataRegStateOrRatChanged(transport, this,
EVENT_DEFAULT_PHONE_DATA_STATE_CHANGED, null);
}
}
// Sets the Voice reg state to STATE_OUT_OF_SERVICE and also queries the data service
// state. We don't ever need the voice reg state to be anything other than in or out of
// service.
setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
mDefaultPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
// Force initial roaming state update later, on EVENT_CARRIER_CONFIG_CHANGED.
// Settings provider or CarrierConfig may not be loaded now.
mDefaultPhone.registerForVolteSilentRedial(this, EVENT_INITIATE_VOLTE_SILENT_REDIAL, null);
}
//todo: get rid of this function. It is not needed since parentPhone obj never changes
@Override
public void dispose() {
logd("dispose");
// Nothing to dispose in Phone
//super.dispose();
mPendingMMIs.clear();
mExternalCallTracker.tearDown();
mCT.unregisterPhoneStateListener(mExternalCallTracker);
mCT.unregisterForVoiceCallEnded(this);
mCT.dispose();
//Force all referenced classes to unregister their former registered events
if (mDefaultPhone != null && mDefaultPhone.getServiceStateTracker() != null) {
for (int transport : mDefaultPhone.getTransportManager().getAvailableTransports()) {
mDefaultPhone.getServiceStateTracker()
.unregisterForDataRegStateOrRatChanged(transport, this);
}
mDefaultPhone.unregisterForServiceStateChanged(this);
}
if (mDefaultPhone != null) {
mDefaultPhone.unregisterForVolteSilentRedial(this);
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public ServiceState getServiceState() {
return mSS;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@VisibleForTesting
public void setServiceState(int state) {
boolean isVoiceRegStateChanged = false;
synchronized (this) {
isVoiceRegStateChanged = mSS.getState() != state;
mSS.setVoiceRegState(state);
}
updateDataServiceState();
if (isVoiceRegStateChanged) {
if (mDefaultPhone.getServiceStateTracker() != null) {
mDefaultPhone.getServiceStateTracker().onImsServiceStateChanged();
}
}
}
@Override
public CallTracker getCallTracker() {
return mCT;
}
public ImsExternalCallTracker getExternalCallTracker() {
return mExternalCallTracker;
}
@Override
public List<? extends ImsPhoneMmiCode>
getPendingMmiCodes() {
return mPendingMMIs;
}
@Override
public void
acceptCall(int videoState) throws CallStateException {
mCT.acceptCall(videoState);
}
@Override
public void
rejectCall() throws CallStateException {
mCT.rejectCall();
}
@Override
public void
switchHoldingAndActive() throws CallStateException {
throw new UnsupportedOperationException("Use hold() and unhold() instead.");
}
@Override
public boolean canConference() {
return mCT.canConference();
}
public boolean canDial() {
try {
mCT.checkForDialIssues();
} catch (CallStateException cse) {
return false;
}
return true;
}
@Override
public void conference() {
mCT.conference();
}
@Override
public void clearDisconnected() {
mCT.clearDisconnected();
}
@Override
public boolean canTransfer() {
return mCT.canTransfer();
}
@Override
public void explicitCallTransfer() throws CallStateException {
mCT.explicitCallTransfer();
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public ImsPhoneCall
getForegroundCall() {
return mCT.mForegroundCall;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public ImsPhoneCall
getBackgroundCall() {
return mCT.mBackgroundCall;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public ImsPhoneCall
getRingingCall() {
return mCT.mRingingCall;
}
@Override
public boolean isImsAvailable() {
return mCT.isImsServiceReady();
}
/**
* Hold the currently active call, possibly unholding a currently held call.
* @throws CallStateException
*/
public void holdActiveCall() throws CallStateException {
mCT.holdActiveCall();
}
/**
* Unhold the currently active call, possibly holding a currently active call.
* If the call tracker is already in the middle of a hold operation, this is a noop.
* @throws CallStateException
*/
public void unholdHeldCall() throws CallStateException {
mCT.unholdHeldCall();
}
private boolean handleCallDeflectionIncallSupplementaryService(
String dialString) {
if (dialString.length() > 1) {
return false;
}
if (getRingingCall().getState() != ImsPhoneCall.State.IDLE) {
if (DBG) logd("MmiCode 0: rejectCall");
try {
mCT.rejectCall();
} catch (CallStateException e) {
if (DBG) Rlog.d(LOG_TAG, "reject failed", e);
notifySuppServiceFailed(Phone.SuppService.REJECT);
}
} else if (getBackgroundCall().getState() != ImsPhoneCall.State.IDLE) {
if (DBG) logd("MmiCode 0: hangupWaitingOrBackground");
try {
mCT.hangup(getBackgroundCall());
} catch (CallStateException e) {
if (DBG) Rlog.d(LOG_TAG, "hangup failed", e);
}
}
return true;
}
private void sendUssdResponse(String ussdRequest, CharSequence message, int returnCode,
ResultReceiver wrappedCallback) {
UssdResponse response = new UssdResponse(ussdRequest, message);
Bundle returnData = new Bundle();
returnData.putParcelable(TelephonyManager.USSD_RESPONSE, response);
wrappedCallback.send(returnCode, returnData);
}
@Override
public boolean handleUssdRequest(String ussdRequest, ResultReceiver wrappedCallback)
throws CallStateException {
if (mPendingMMIs.size() > 0) {
// There are MMI codes in progress; fail attempt now.
logi("handleUssdRequest: queue full: " + Rlog.pii(LOG_TAG, ussdRequest));
sendUssdResponse(ussdRequest, null, TelephonyManager.USSD_RETURN_FAILURE,
wrappedCallback );
return true;
}
try {
dialInternal(ussdRequest, new ImsDialArgs.Builder().build(), wrappedCallback);
} catch (CallStateException cse) {
if (CS_FALLBACK.equals(cse.getMessage())) {
throw cse;
} else {
Rlog.w(LOG_TAG, "Could not execute USSD " + cse);
sendUssdResponse(ussdRequest, null, TelephonyManager.USSD_RETURN_FAILURE,
wrappedCallback);
}
} catch (Exception e) {
Rlog.w(LOG_TAG, "Could not execute USSD " + e);
sendUssdResponse(ussdRequest, null, TelephonyManager.USSD_RETURN_FAILURE,
wrappedCallback);
return false;
}
return true;
}
private boolean handleCallWaitingIncallSupplementaryService(
String dialString) {
int len = dialString.length();
if (len > 2) {
return false;
}
ImsPhoneCall call = getForegroundCall();
try {
if (len > 1) {
if (DBG) logd("not support 1X SEND");
notifySuppServiceFailed(Phone.SuppService.HANGUP);
} else {
if (call.getState() != ImsPhoneCall.State.IDLE) {
if (DBG) logd("MmiCode 1: hangup foreground");
mCT.hangup(call);
} else {
if (DBG) logd("MmiCode 1: holdActiveCallForWaitingCall");
mCT.holdActiveCallForWaitingCall();
}
}
} catch (CallStateException e) {
if (DBG) Rlog.d(LOG_TAG, "hangup failed", e);
notifySuppServiceFailed(Phone.SuppService.HANGUP);
}
return true;
}
private boolean handleCallHoldIncallSupplementaryService(String dialString) {
int len = dialString.length();
if (len > 2) {
return false;
}
if (len > 1) {
if (DBG) logd("separate not supported");
notifySuppServiceFailed(Phone.SuppService.SEPARATE);
} else {
try {
if (getRingingCall().getState() != ImsPhoneCall.State.IDLE) {
if (DBG) logd("MmiCode 2: accept ringing call");
mCT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
} else if (getBackgroundCall().getState() == ImsPhoneCall.State.HOLDING) {
// If there's an active ongoing call as well, hold it and the background one
// should automatically unhold. Otherwise just unhold the background call.
if (getForegroundCall().getState() != ImsPhoneCall.State.IDLE) {
if (DBG) logd("MmiCode 2: switch holding and active");
mCT.holdActiveCall();
} else {
if (DBG) logd("MmiCode 2: unhold held call");
mCT.unholdHeldCall();
}
} else if (getForegroundCall().getState() != ImsPhoneCall.State.IDLE) {
if (DBG) logd("MmiCode 2: hold active call");
mCT.holdActiveCall();
}
} catch (CallStateException e) {
if (DBG) Rlog.d(LOG_TAG, "switch failed", e);
notifySuppServiceFailed(Phone.SuppService.SWITCH);
}
}
return true;
}
private boolean handleMultipartyIncallSupplementaryService(
String dialString) {
if (dialString.length() > 1) {
return false;
}
if (DBG) logd("MmiCode 3: merge calls");
conference();
return true;
}
private boolean handleEctIncallSupplementaryService(String dialString) {
if (dialString.length() != 1) {
return false;
}
if (DBG) logd("MmiCode 4: explicit call transfer");
try {
explicitCallTransfer();
} catch (CallStateException e) {
if (DBG) Rlog.d(LOG_TAG, "explicit call transfer failed", e);
notifySuppServiceFailed(Phone.SuppService.TRANSFER);
}
return true;
}
private boolean handleCcbsIncallSupplementaryService(String dialString) {
if (dialString.length() > 1) {
return false;
}
logi("MmiCode 5: CCBS not supported!");
// Treat it as an "unknown" service.
notifySuppServiceFailed(Phone.SuppService.UNKNOWN);
return true;
}
public void notifySuppSvcNotification(SuppServiceNotification suppSvc) {
logd("notifySuppSvcNotification: suppSvc = " + suppSvc);
AsyncResult ar = new AsyncResult(null, suppSvc, null);
mSsnRegistrants.notifyRegistrants(ar);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public boolean handleInCallMmiCommands(String dialString) {
if (!isInCall()) {
return false;
}
if (TextUtils.isEmpty(dialString)) {
return false;
}
boolean result = false;
char ch = dialString.charAt(0);
switch (ch) {
case '0':
result = handleCallDeflectionIncallSupplementaryService(
dialString);
break;
case '1':
result = handleCallWaitingIncallSupplementaryService(
dialString);
break;
case '2':
result = handleCallHoldIncallSupplementaryService(dialString);
break;
case '3':
result = handleMultipartyIncallSupplementaryService(dialString);
break;
case '4':
result = handleEctIncallSupplementaryService(dialString);
break;
case '5':
result = handleCcbsIncallSupplementaryService(dialString);
break;
default:
break;
}
return result;
}
boolean isInCall() {
ImsPhoneCall.State foregroundCallState = getForegroundCall().getState();
ImsPhoneCall.State backgroundCallState = getBackgroundCall().getState();
ImsPhoneCall.State ringingCallState = getRingingCall().getState();
return (foregroundCallState.isAlive() ||
backgroundCallState.isAlive() ||
ringingCallState.isAlive());
}
@Override
public boolean isInImsEcm() {
return mIsInImsEcm;
}
@Override
public boolean isInEcm() {
return mDefaultPhone.isInEcm();
}
@Override
public void setIsInEcm(boolean isInEcm){
mIsInImsEcm = isInEcm;
mDefaultPhone.setIsInEcm(isInEcm);
}
public void notifyNewRingingConnection(Connection c) {
mDefaultPhone.notifyNewRingingConnectionP(c);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void notifyUnknownConnection(Connection c) {
mDefaultPhone.notifyUnknownConnectionP(c);
}
@Override
public void notifyForVideoCapabilityChanged(boolean isVideoCapable) {
mIsVideoCapable = isVideoCapable;
mDefaultPhone.notifyForVideoCapabilityChanged(isVideoCapable);
}
@Override
public void setRadioPower(boolean on, boolean forEmergencyCall,
boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
mDefaultPhone.setRadioPower(on, forEmergencyCall, isSelectedPhoneForEmergencyCall,
forceApply);
}
@Override
public Connection startConference(String[] participantsToDial, DialArgs dialArgs)
throws CallStateException {
ImsDialArgs.Builder imsDialArgsBuilder;
imsDialArgsBuilder = ImsDialArgs.Builder.from(dialArgs);
return mCT.startConference(participantsToDial, imsDialArgsBuilder.build());
}
@Override
public Connection dial(String dialString, DialArgs dialArgs,
Consumer<Phone> chosenPhoneConsumer) throws CallStateException {
chosenPhoneConsumer.accept(this);
return dialInternal(dialString, dialArgs, null);
}
private Connection dialInternal(String dialString, DialArgs dialArgs,
ResultReceiver wrappedCallback)
throws CallStateException {
mLastDialString = dialString;
// Need to make sure dialString gets parsed properly
String newDialString = PhoneNumberUtils.stripSeparators(dialString);
// handle in-call MMI first if applicable
if (handleInCallMmiCommands(newDialString)) {
return null;
}
ImsDialArgs.Builder imsDialArgsBuilder;
imsDialArgsBuilder = ImsDialArgs.Builder.from(dialArgs);
// Get the CLIR info if needed
imsDialArgsBuilder.setClirMode(mCT.getClirMode());
if (mDefaultPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
return mCT.dial(dialString, imsDialArgsBuilder.build());
}
// Only look at the Network portion for mmi
String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
ImsPhoneMmiCode mmi =
ImsPhoneMmiCode.newFromDialString(networkPortion, this, wrappedCallback);
if (DBG) logd("dialInternal: dialing w/ mmi '" + mmi + "'...");
if (mmi == null) {
return mCT.dial(dialString, imsDialArgsBuilder.build());
} else if (mmi.isTemporaryModeCLIR()) {
imsDialArgsBuilder.setClirMode(mmi.getCLIRMode());
return mCT.dial(mmi.getDialingNumber(), imsDialArgsBuilder.build());
} else if (!mmi.isSupportedOverImsPhone()) {
// If the mmi is not supported by IMS service,
// try to initiate dialing with default phone
// Note: This code is never reached; there is a bug in isSupportedOverImsPhone which
// causes it to return true even though the "processCode" method ultimately throws the
// exception.
logi("dialInternal: USSD not supported by IMS; fallback to CS.");
throw new CallStateException(CS_FALLBACK);
} else {
mPendingMMIs.add(mmi);
mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
try {
mmi.processCode();
} catch (CallStateException cse) {
if (CS_FALLBACK.equals(cse.getMessage())) {
logi("dialInternal: fallback to GSM required.");
// Make sure we remove from the list of pending MMIs since it will handover to
// GSM.
mPendingMMIs.remove(mmi);
throw cse;
}
}
return null;
}
}
@Override
public void
sendDtmf(char c) {
if (!PhoneNumberUtils.is12Key(c)) {
loge("sendDtmf called with invalid character '" + c + "'");
} else {
if (mCT.getState() == PhoneConstants.State.OFFHOOK) {
mCT.sendDtmf(c, null);
}
}
}
@Override
public void
startDtmf(char c) {
if (!(PhoneNumberUtils.is12Key(c) || (c >= 'A' && c <= 'D'))) {
loge("startDtmf called with invalid character '" + c + "'");
} else {
mCT.startDtmf(c);
}
}
@Override
public void
stopDtmf() {
mCT.stopDtmf();
}
public void notifyIncomingRing() {
if (DBG) logd("notifyIncomingRing");
AsyncResult ar = new AsyncResult(null, null, null);
sendMessage(obtainMessage(EVENT_CALL_RING, ar));
}
@Override
public void setMute(boolean muted) {
mCT.setMute(muted);
}
@Override
public void setTTYMode(int ttyMode, Message onComplete) {
mCT.setTtyMode(ttyMode);
}
@Override
public void setUiTTYMode(int uiTtyMode, Message onComplete) {
mCT.setUiTTYMode(uiTtyMode, onComplete);
}
@Override
public boolean getMute() {
return mCT.getMute();
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public PhoneConstants.State getState() {
return mCT.getState();
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private boolean isValidCommandInterfaceCFReason (int commandInterfaceCFReason) {
switch (commandInterfaceCFReason) {
case CF_REASON_UNCONDITIONAL:
case CF_REASON_BUSY:
case CF_REASON_NO_REPLY:
case CF_REASON_NOT_REACHABLE:
case CF_REASON_ALL:
case CF_REASON_ALL_CONDITIONAL:
return true;
default:
return false;
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private boolean isValidCommandInterfaceCFAction (int commandInterfaceCFAction) {
switch (commandInterfaceCFAction) {
case CF_ACTION_DISABLE:
case CF_ACTION_ENABLE:
case CF_ACTION_REGISTRATION:
case CF_ACTION_ERASURE:
return true;
default:
return false;
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private boolean isCfEnable(int action) {
return (action == CF_ACTION_ENABLE) || (action == CF_ACTION_REGISTRATION);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private int getConditionFromCFReason(int reason) {
switch(reason) {
case CF_REASON_UNCONDITIONAL: return ImsUtInterface.CDIV_CF_UNCONDITIONAL;
case CF_REASON_BUSY: return ImsUtInterface.CDIV_CF_BUSY;
case CF_REASON_NO_REPLY: return ImsUtInterface.CDIV_CF_NO_REPLY;
case CF_REASON_NOT_REACHABLE: return ImsUtInterface.CDIV_CF_NOT_REACHABLE;
case CF_REASON_ALL: return ImsUtInterface.CDIV_CF_ALL;
case CF_REASON_ALL_CONDITIONAL: return ImsUtInterface.CDIV_CF_ALL_CONDITIONAL;
default:
break;
}
return ImsUtInterface.INVALID;
}
private int getCFReasonFromCondition(int condition) {
switch(condition) {
case ImsUtInterface.CDIV_CF_UNCONDITIONAL: return CF_REASON_UNCONDITIONAL;
case ImsUtInterface.CDIV_CF_BUSY: return CF_REASON_BUSY;
case ImsUtInterface.CDIV_CF_NO_REPLY: return CF_REASON_NO_REPLY;
case ImsUtInterface.CDIV_CF_NOT_REACHABLE: return CF_REASON_NOT_REACHABLE;
case ImsUtInterface.CDIV_CF_ALL: return CF_REASON_ALL;
case ImsUtInterface.CDIV_CF_ALL_CONDITIONAL: return CF_REASON_ALL_CONDITIONAL;
default:
break;
}
return CF_REASON_NOT_REACHABLE;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private int getActionFromCFAction(int action) {
switch(action) {
case CF_ACTION_DISABLE: return ImsUtInterface.ACTION_DEACTIVATION;
case CF_ACTION_ENABLE: return ImsUtInterface.ACTION_ACTIVATION;
case CF_ACTION_ERASURE: return ImsUtInterface.ACTION_ERASURE;
case CF_ACTION_REGISTRATION: return ImsUtInterface.ACTION_REGISTRATION;
default:
break;
}
return ImsUtInterface.INVALID;
}
@Override
public void getOutgoingCallerIdDisplay(Message onComplete) {
if (DBG) logd("getCLIR");
Message resp;
SS ss = new SS(onComplete);
resp = obtainMessage(EVENT_GET_CLIR_DONE, ss);
try {
ImsUtInterface ut = mCT.getUtInterface();
ut.queryCLIR(resp);
} catch (ImsException e) {
sendErrorResponse(onComplete, e);
}
}
@Override
public void setOutgoingCallerIdDisplay(int clirMode, Message onComplete) {
if (DBG) logd("setCLIR action= " + clirMode);
Message resp;
// Packing CLIR value in the message. This will be required for
// SharedPreference caching, if the message comes back as part of
// a success response.
SS ss = new SS(clirMode, onComplete);
resp = obtainMessage(EVENT_SET_CLIR_DONE, ss);
try {
ImsUtInterface ut = mCT.getUtInterface();
ut.updateCLIR(clirMode, resp);
} catch (ImsException e) {
sendErrorResponse(onComplete, e);
}
}
@Override
public void queryCLIP(Message onComplete) {
Message resp;
SS ss = new SS(onComplete);
resp = obtainMessage(EVENT_GET_CLIP_DONE, ss);
try {
Rlog.d(LOG_TAG, "ut.queryCLIP");
ImsUtInterface ut = mCT.getUtInterface();
ut.queryCLIP(resp);
} catch (ImsException e) {
sendErrorResponse(onComplete, e);
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public void getCallForwardingOption(int commandInterfaceCFReason,
Message onComplete) {
getCallForwardingOption(commandInterfaceCFReason,
SERVICE_CLASS_VOICE, onComplete);
}
@Override
public void getCallForwardingOption(int commandInterfaceCFReason, int serviceClass,
Message onComplete) {
if (DBG) logd("getCallForwardingOption reason=" + commandInterfaceCFReason);
if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
if (DBG) logd("requesting call forwarding query.");
Message resp;
SS ss = new SS(commandInterfaceCFReason, serviceClass, onComplete);
resp = obtainMessage(EVENT_GET_CALL_FORWARD_DONE, ss);
try {
ImsUtInterface ut = mCT.getUtInterface();
ut.queryCallForward(getConditionFromCFReason(commandInterfaceCFReason), null, resp);
} catch (ImsException e) {
sendErrorResponse(onComplete, e);
}
} else if (onComplete != null) {
sendErrorResponse(onComplete);
}
}
@Override
public void setCallForwardingOption(int commandInterfaceCFAction,
int commandInterfaceCFReason,
String dialingNumber,
int timerSeconds,
Message onComplete) {
setCallForwardingOption(commandInterfaceCFAction, commandInterfaceCFReason, dialingNumber,
CommandsInterface.SERVICE_CLASS_VOICE, timerSeconds, onComplete);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public void setCallForwardingOption(int commandInterfaceCFAction,
int commandInterfaceCFReason,
String dialingNumber,
int serviceClass,
int timerSeconds,
Message onComplete) {
if (DBG) {
logd("setCallForwardingOption action=" + commandInterfaceCFAction
+ ", reason=" + commandInterfaceCFReason + " serviceClass=" + serviceClass);
}
if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) &&
(isValidCommandInterfaceCFReason(commandInterfaceCFReason))) {
Message resp;
SS ss = new SS(commandInterfaceCFAction, commandInterfaceCFReason,
dialingNumber, serviceClass, timerSeconds, onComplete);
resp = obtainMessage(EVENT_SET_CALL_FORWARD_DONE, ss);
try {
ImsUtInterface ut = mCT.getUtInterface();
ut.updateCallForward(getActionFromCFAction(commandInterfaceCFAction),
getConditionFromCFReason(commandInterfaceCFReason),
dialingNumber,
serviceClass,
timerSeconds,
resp);
} catch (ImsException e) {
sendErrorResponse(onComplete, e);
}
} else if (onComplete != null) {
sendErrorResponse(onComplete);
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public void getCallWaiting(Message onComplete) {
if (DBG) logd("getCallWaiting");
Message resp;
SS ss = new SS(onComplete);
resp = obtainMessage(EVENT_GET_CALL_WAITING_DONE, ss);
try {
ImsUtInterface ut = mCT.getUtInterface();
ut.queryCallWaiting(resp);
} catch (ImsException e) {
sendErrorResponse(onComplete, e);
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public void setCallWaiting(boolean enable, Message onComplete) {
int serviceClass = CommandsInterface.SERVICE_CLASS_VOICE;
CarrierConfigManager configManager = (CarrierConfigManager)
getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
PersistableBundle b = configManager.getConfigForSubId(getSubId());
if (b != null) {
serviceClass = b.getInt(CarrierConfigManager.KEY_CALL_WAITING_SERVICE_CLASS_INT,
CommandsInterface.SERVICE_CLASS_VOICE);
}
setCallWaiting(enable, serviceClass, onComplete);
}
public void setCallWaiting(boolean enable, int serviceClass, Message onComplete) {
if (DBG) logd("setCallWaiting enable=" + enable);
Message resp;
SS ss = new SS(enable, serviceClass, onComplete);
resp = obtainMessage(EVENT_SET_CALL_WAITING_DONE, ss);
try {
ImsUtInterface ut = mCT.getUtInterface();
ut.updateCallWaiting(enable, serviceClass, resp);
} catch (ImsException e) {
sendErrorResponse(onComplete, e);
}
}
private int getCBTypeFromFacility(String facility) {
if (CB_FACILITY_BAOC.equals(facility)) {
return ImsUtImplBase.CALL_BARRING_ALL_OUTGOING;
} else if (CB_FACILITY_BAOIC.equals(facility)) {
return ImsUtImplBase.CALL_BARRING_OUTGOING_INTL;
} else if (CB_FACILITY_BAOICxH.equals(facility)) {
return ImsUtImplBase.CALL_BARRING_OUTGOING_INTL_EXCL_HOME;
} else if (CB_FACILITY_BAIC.equals(facility)) {
return ImsUtImplBase.CALL_BARRING_ALL_INCOMING;
} else if (CB_FACILITY_BAICr.equals(facility)) {
return ImsUtImplBase.CALL_BLOCKING_INCOMING_WHEN_ROAMING;
} else if (CB_FACILITY_BA_ALL.equals(facility)) {
return ImsUtImplBase.CALL_BARRING_ALL;
} else if (CB_FACILITY_BA_MO.equals(facility)) {
return ImsUtImplBase.CALL_BARRING_OUTGOING_ALL_SERVICES;
} else if (CB_FACILITY_BA_MT.equals(facility)) {
return ImsUtImplBase.CALL_BARRING_INCOMING_ALL_SERVICES;
} else if (CB_FACILITY_BIC_ACR.equals(facility)) {
return ImsUtImplBase.CALL_BARRING_ANONYMOUS_INCOMING;
}
return 0;
}
public void getCallBarring(String facility, Message onComplete) {
getCallBarring(facility, onComplete, CommandsInterface.SERVICE_CLASS_VOICE);
}
public void getCallBarring(String facility, Message onComplete, int serviceClass) {
getCallBarring(facility, "", onComplete, serviceClass);
}
@Override
public void getCallBarring(String facility, String password, Message onComplete,
int serviceClass) {
if (DBG) logd("getCallBarring facility=" + facility + ", serviceClass = " + serviceClass);
Message resp;
SS ss = new SS(facility, password, serviceClass, onComplete);
resp = obtainMessage(EVENT_GET_CALL_BARRING_DONE, ss);
try {
ImsUtInterface ut = mCT.getUtInterface();
// password is not required with Ut interface
ut.queryCallBarring(getCBTypeFromFacility(facility), resp, serviceClass);
} catch (ImsException e) {
sendErrorResponse(onComplete, e);
}
}
public void setCallBarring(String facility, boolean lockState, String password,
Message onComplete) {
setCallBarring(facility, lockState, password, onComplete,
CommandsInterface.SERVICE_CLASS_VOICE);
}
@Override
public void setCallBarring(String facility, boolean lockState, String password,
Message onComplete, int serviceClass) {
if (DBG) {
logd("setCallBarring facility=" + facility
+ ", lockState=" + lockState + ", serviceClass = " + serviceClass);
}
Message resp;
SS ss = new SS(facility, lockState, password, serviceClass, onComplete);
resp = obtainMessage(EVENT_SET_CALL_BARRING_DONE, ss);
int action;
if (lockState) {
action = CommandsInterface.CF_ACTION_ENABLE;
}
else {
action = CommandsInterface.CF_ACTION_DISABLE;
}
try {
ImsUtInterface ut = mCT.getUtInterface();
ut.updateCallBarring(getCBTypeFromFacility(facility), action,
resp, null, serviceClass, password);
} catch (ImsException e) {
sendErrorResponse(onComplete, e);
}
}
@Override
public void sendUssdResponse(String ussdMessge) {
logd("sendUssdResponse");
ImsPhoneMmiCode mmi = ImsPhoneMmiCode.newFromUssdUserInput(ussdMessge, this);
mPendingMMIs.add(mmi);
mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
mmi.sendUssd(ussdMessge);
}
public void sendUSSD(String ussdString, Message response) {
Rlog.d(LOG_TAG, "sendUssd ussdString = " + ussdString);
mLastDialString = ussdString;
mCT.sendUSSD(ussdString, response);
}
@Override
public void cancelUSSD(Message msg) {
mCT.cancelUSSD(msg);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void sendErrorResponse(Message onComplete) {
logd("sendErrorResponse");
if (onComplete != null) {
AsyncResult.forMessage(onComplete, null,
new CommandException(CommandException.Error.GENERIC_FAILURE));
onComplete.sendToTarget();
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@VisibleForTesting
public void sendErrorResponse(Message onComplete, Throwable e) {
logd("sendErrorResponse");
if (onComplete != null) {
AsyncResult.forMessage(onComplete, null, getCommandException(e));
onComplete.sendToTarget();
}
}
private CommandException getCommandException(int code, String errorString) {
logd("getCommandException code= " + code + ", errorString= " + errorString);
CommandException.Error error = CommandException.Error.GENERIC_FAILURE;
switch(code) {
case ImsReasonInfo.CODE_UT_NOT_SUPPORTED:
error = CommandException.Error.REQUEST_NOT_SUPPORTED;
break;
case ImsReasonInfo.CODE_UT_CB_PASSWORD_MISMATCH:
error = CommandException.Error.PASSWORD_INCORRECT;
break;
case ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE:
error = CommandException.Error.RADIO_NOT_AVAILABLE;
break;
case ImsReasonInfo.CODE_FDN_BLOCKED:
error = CommandException.Error.FDN_CHECK_FAILURE;
break;
case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_DIAL:
error = CommandException.Error.SS_MODIFIED_TO_DIAL;
break;
case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_USSD:
error = CommandException.Error.SS_MODIFIED_TO_USSD;
break;
case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_SS:
error = CommandException.Error.SS_MODIFIED_TO_SS;
break;
case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO:
error = CommandException.Error.SS_MODIFIED_TO_DIAL_VIDEO;
break;
default:
break;
}
return new CommandException(error, errorString);
}
private CommandException getCommandException(Throwable e) {
CommandException ex = null;
if (e instanceof ImsException) {
ex = getCommandException(((ImsException)e).getCode(), e.getMessage());
} else {
logd("getCommandException generic failure");
ex = new CommandException(CommandException.Error.GENERIC_FAILURE);
}
return ex;
}
private void
onNetworkInitiatedUssd(ImsPhoneMmiCode mmi) {
logd("onNetworkInitiatedUssd");
mMmiCompleteRegistrants.notifyRegistrants(
new AsyncResult(null, mmi, null));
}
/* package */
void onIncomingUSSD(int ussdMode, String ussdMessage) {
if (DBG) logd("onIncomingUSSD ussdMode=" + ussdMode);
boolean isUssdError;
boolean isUssdRequest;
isUssdRequest
= (ussdMode == CommandsInterface.USSD_MODE_REQUEST);
isUssdError
= (ussdMode != CommandsInterface.USSD_MODE_NOTIFY
&& ussdMode != CommandsInterface.USSD_MODE_REQUEST);
ImsPhoneMmiCode found = null;
for (int i = 0, s = mPendingMMIs.size() ; i < s; i++) {
if(mPendingMMIs.get(i).isPendingUSSD()) {
found = mPendingMMIs.get(i);
break;
}
}
if (found != null) {
// Complete pending USSD
if (isUssdError) {
found.onUssdFinishedError();
} else {
found.onUssdFinished(ussdMessage, isUssdRequest);
}
} else if (!isUssdError && !TextUtils.isEmpty(ussdMessage)) {
// pending USSD not found
// The network may initiate its own USSD request
// ignore everything that isnt a Notify or a Request
// also, discard if there is no message to present
ImsPhoneMmiCode mmi;
mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(ussdMessage,
isUssdRequest,
this);
onNetworkInitiatedUssd(mmi);
} else if (isUssdError) {
ImsPhoneMmiCode mmi;
mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(ussdMessage,
true,
this);
mmi.onUssdFinishedError();
}
}
/**
* Removes the given MMI from the pending list and notifies
* registrants that it is complete.
* @param mmi MMI that is done
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void onMMIDone(ImsPhoneMmiCode mmi) {
/* Only notify complete if it's on the pending list.
* Otherwise, it's already been handled (eg, previously canceled).
* The exception is cancellation of an incoming USSD-REQUEST, which is
* not on the list.
*/
logd("onMMIDone: mmi=" + mmi);
if (mPendingMMIs.remove(mmi) || mmi.isUssdRequest() || mmi.isSsInfo()) {
ResultReceiver receiverCallback = mmi.getUssdCallbackReceiver();
if (receiverCallback != null) {
int returnCode = (mmi.getState() == MmiCode.State.COMPLETE) ?
TelephonyManager.USSD_RETURN_SUCCESS : TelephonyManager.USSD_RETURN_FAILURE;
sendUssdResponse(mmi.getDialString(), mmi.getMessage(), returnCode,
receiverCallback );
} else {
logv("onMMIDone: notifyRegistrants");
mMmiCompleteRegistrants.notifyRegistrants(
new AsyncResult(null, mmi, null));
}
}
}
@Override
public ArrayList<Connection> getHandoverConnection() {
ArrayList<Connection> connList = new ArrayList<Connection>();
// Add all foreground call connections
connList.addAll(getForegroundCall().getConnections());
// Add all background call connections
connList.addAll(getBackgroundCall().getConnections());
// Add all background call connections
connList.addAll(getRingingCall().getConnections());
if (connList.size() > 0) {
return connList;
} else {
return null;
}
}
@Override
public void notifySrvccState(Call.SrvccState state) {
mCT.notifySrvccState(state);
}
/* package */ void
initiateSilentRedial() {
initiateSilentRedial(false, EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED);
}
/* package */ void
initiateSilentRedial(boolean isEmergency, int eccCategory) {
DialArgs dialArgs = new DialArgs.Builder()
.setIsEmergency(isEmergency)
.setEccCategory(eccCategory)
.build();
int cause = CallFailCause.LOCAL_CALL_CS_RETRY_REQUIRED;
AsyncResult ar = new AsyncResult(null,
new SilentRedialParam(mLastDialString, cause, dialArgs),
null);
if (ar != null) {
// There is a race condition that can happen in some cases:
// (Main thread) dial start
// (Binder Thread) onCallSessionFailed
// (Binder Thread) schedule a redial for CS on the main thread
// (Main Thread) dial finish
// (Main Thread) schedule to associate ImsPhoneConnection with
// GsmConnection on the main thread
// If scheduling the CS redial occurs before the command to schedule the
// ImsPhoneConnection to be associated with the GsmConnection, the CS redial will occur
// before GsmConnection has had callbacks to ImsPhone correctly updated. This will cause
// Callbacks back to GsmCdmaPhone to never be set up correctly and we will lose track of
// the instance.
// Instead, schedule this redial to happen on the main thread, so that we know dial has
// finished before scheduling a redial:
// (Main thread) dial start
// (Binder Thread) onCallSessionFailed -> move notify registrants to main thread
// (Main Thread) dial finish
// (Main Thread) schedule on main thread to associate ImsPhoneConnection with
// GsmConnection
// (Main Thread) schedule a redial for CS
mContext.getMainExecutor().execute(() -> {
logd("initiateSilentRedial: notifying registrants, isEmergency=" + isEmergency
+ ", eccCategory=" + eccCategory);
mSilentRedialRegistrants.notifyRegistrants(ar);
});
}
}
@Override
public void registerForSilentRedial(Handler h, int what, Object obj) {
mSilentRedialRegistrants.addUnique(h, what, obj);
}
@Override
public void unregisterForSilentRedial(Handler h) {
mSilentRedialRegistrants.remove(h);
}
@Override
public void registerForSuppServiceNotification(Handler h, int what, Object obj) {
mSsnRegistrants.addUnique(h, what, obj);
}
@Override
public void unregisterForSuppServiceNotification(Handler h) {
mSsnRegistrants.remove(h);
}
@Override
public int getSubId() {
return mDefaultPhone.getSubId();
}
@Override
public int getPhoneId() {
return mDefaultPhone.getPhoneId();
}
private CallForwardInfo getCallForwardInfo(ImsCallForwardInfo info) {
CallForwardInfo cfInfo = new CallForwardInfo();
cfInfo.status = info.getStatus();
cfInfo.reason = getCFReasonFromCondition(info.getCondition());
cfInfo.serviceClass = SERVICE_CLASS_VOICE;
cfInfo.toa = info.getToA();
cfInfo.number = info.getNumber();
cfInfo.timeSeconds = info.getTimeSeconds();
return cfInfo;
}
@Override
public String getLine1Number() {
return mDefaultPhone.getLine1Number();
}
/**
* Used to Convert ImsCallForwardInfo[] to CallForwardInfo[].
* Update received call forward status to default IccRecords.
*/
public CallForwardInfo[] handleCfQueryResult(ImsCallForwardInfo[] infos) {
CallForwardInfo[] cfInfos = null;
if (infos != null && infos.length != 0) {
cfInfos = new CallForwardInfo[infos.length];
}
if (infos == null || infos.length == 0) {
// Assume the default is not active
// Set unconditional CFF in SIM to false
setVoiceCallForwardingFlag(getIccRecords(), 1, false, null);
} else {
for (int i = 0, s = infos.length; i < s; i++) {
if (infos[i].getCondition() == ImsUtInterface.CDIV_CF_UNCONDITIONAL) {
setVoiceCallForwardingFlag(getIccRecords(), 1, (infos[i].getStatus() == 1),
infos[i].getNumber());
}
cfInfos[i] = getCallForwardInfo(infos[i]);
}
}
return cfInfos;
}
private int[] handleCbQueryResult(ImsSsInfo[] infos) {
int[] cbInfos = new int[1];
cbInfos[0] = SERVICE_CLASS_NONE;
if (infos[0].getStatus() == 1) {
cbInfos[0] = SERVICE_CLASS_VOICE;
}
return cbInfos;
}
private int[] handleCwQueryResult(ImsSsInfo[] infos) {
int[] cwInfos = new int[2];
cwInfos[0] = 0;
if (infos[0].getStatus() == 1) {
cwInfos[0] = 1;
cwInfos[1] = SERVICE_CLASS_VOICE;
}
return cwInfos;
}
private void
sendResponse(Message onComplete, Object result, Throwable e) {
if (onComplete != null) {
CommandException ex = null;
if (e != null) {
ex = getCommandException(e);
}
AsyncResult.forMessage(onComplete, result, ex);
onComplete.sendToTarget();
}
}
private void updateDataServiceState() {
if (mSS != null && mDefaultPhone.getServiceStateTracker() != null
&& mDefaultPhone.getServiceStateTracker().mSS != null) {
ServiceState ss = mDefaultPhone.getServiceStateTracker().mSS;
mSS.setDataRegState(ss.getDataRegistrationState());
List<NetworkRegistrationInfo> nriList =
ss.getNetworkRegistrationInfoListForDomain(NetworkRegistrationInfo.DOMAIN_PS);
for (NetworkRegistrationInfo nri : nriList) {
mSS.addNetworkRegistrationInfo(nri);
}
mSS.setIwlanPreferred(ss.isIwlanPreferred());
logd("updateDataServiceState: defSs = " + ss + " imsSs = " + mSS);
}
}
boolean isCsRetryException(Throwable e) {
if ((e != null) && (e instanceof ImsException)
&& (((ImsException)e).getCode()
== ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED)) {
return true;
}
return false;
}
private Bundle setCsfbBundle(boolean isCsRetry) {
Bundle b = new Bundle();
b.putBoolean(CS_FALLBACK_SS, isCsRetry);
return b;
}
private void sendResponseOrRetryOnCsfbSs(SS ss, int what, Throwable e, Object obj) {
if (!isCsRetryException(e)) {
sendResponse(ss.mOnComplete, obj, e);
return;
}
Rlog.d(LOG_TAG, "Try CSFB: " + what);
ss.mOnComplete.setData(setCsfbBundle(true));
switch (what) {
case EVENT_GET_CALL_FORWARD_DONE:
mDefaultPhone.getCallForwardingOption(ss.mCfReason,
ss.mServiceClass,
ss.mOnComplete);
break;
case EVENT_SET_CALL_FORWARD_DONE:
mDefaultPhone.setCallForwardingOption(ss.mCfAction,
ss.mCfReason,
ss.mDialingNumber,
ss.mServiceClass,
ss.mTimerSeconds,
ss.mOnComplete);
break;
case EVENT_GET_CALL_BARRING_DONE:
mDefaultPhone.getCallBarring(ss.mFacility,
ss.mPassword,
ss.mOnComplete,
ss.mServiceClass);
break;
case EVENT_SET_CALL_BARRING_DONE:
mDefaultPhone.setCallBarring(ss.mFacility,
ss.mLockState,
ss.mPassword,
ss.mOnComplete,
ss.mServiceClass);
break;
case EVENT_GET_CALL_WAITING_DONE:
mDefaultPhone.getCallWaiting(ss.mOnComplete);
break;
case EVENT_SET_CALL_WAITING_DONE:
mDefaultPhone.setCallWaiting(ss.mEnable,
ss.mServiceClass,
ss.mOnComplete);
break;
case EVENT_GET_CLIR_DONE:
mDefaultPhone.getOutgoingCallerIdDisplay(ss.mOnComplete);
break;
case EVENT_SET_CLIR_DONE:
mDefaultPhone.setOutgoingCallerIdDisplay(ss.mClirMode, ss.mOnComplete);
break;
case EVENT_GET_CLIP_DONE:
mDefaultPhone.queryCLIP(ss.mOnComplete);
break;
default:
break;
}
}
@Override
public void handleMessage(Message msg) {
AsyncResult ar = (AsyncResult) msg.obj;
Message onComplete;
SS ss = null;
if (ar != null && ar.userObj instanceof SS) {
ss = (SS) ar.userObj;
}
if (DBG) logd("handleMessage what=" + msg.what);
switch (msg.what) {
case EVENT_SET_CALL_FORWARD_DONE:
if (ar.exception == null && ss != null &&
(ss.mCfReason == CF_REASON_UNCONDITIONAL)) {
setVoiceCallForwardingFlag(getIccRecords(), 1, isCfEnable(ss.mCfAction),
ss.mDialingNumber);
}
if (ss != null) {
sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, null);
}
break;
case EVENT_GET_CALL_FORWARD_DONE:
CallForwardInfo[] cfInfos = null;
if (ar.exception == null) {
cfInfos = handleCfQueryResult((ImsCallForwardInfo[])ar.result);
}
if (ss != null) {
sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, cfInfos);
}
break;
case EVENT_GET_CALL_BARRING_DONE:
case EVENT_GET_CALL_WAITING_DONE:
int[] ssInfos = null;
if (ar.exception == null) {
if (msg.what == EVENT_GET_CALL_BARRING_DONE) {
ssInfos = handleCbQueryResult((ImsSsInfo[])ar.result);
} else if (msg.what == EVENT_GET_CALL_WAITING_DONE) {
ssInfos = handleCwQueryResult((ImsSsInfo[])ar.result);
}
}
if (ss != null) {
sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, ssInfos);
}
break;
case EVENT_GET_CLIR_DONE:
ImsSsInfo ssInfo = (ImsSsInfo) ar.result;
int[] clirInfo = null;
if (ssInfo != null) {
// Unfortunately callers still use the old {n,m} format of ImsSsInfo, so return
// that for compatibility
clirInfo = ssInfo.getCompatArray(ImsSsData.SS_CLIR);
}
if (ss != null) {
sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, clirInfo);
}
break;
case EVENT_GET_CLIP_DONE:
ImsSsInfo ssInfoResp = null;
if (ar.exception == null && ar.result instanceof ImsSsInfo) {
ssInfoResp = (ImsSsInfo) ar.result;
}
if (ss != null) {
sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, ssInfoResp);
}
break;
case EVENT_SET_CLIR_DONE:
if (ar.exception == null) {
if (ss != null) {
saveClirSetting(ss.mClirMode);
}
}
// (Intentional fallthrough)
case EVENT_SET_CALL_BARRING_DONE:
case EVENT_SET_CALL_WAITING_DONE:
if (ss != null) {
sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, null);
}
break;
case EVENT_DEFAULT_PHONE_DATA_STATE_CHANGED:
if (DBG) logd("EVENT_DEFAULT_PHONE_DATA_STATE_CHANGED");
updateDataServiceState();
break;
case EVENT_SERVICE_STATE_CHANGED:
if (VDBG) logd("EVENT_SERVICE_STATE_CHANGED");
ar = (AsyncResult) msg.obj;
ServiceState newServiceState = (ServiceState) ar.result;
updateRoamingState(newServiceState);
break;
case EVENT_VOICE_CALL_ENDED:
if (DBG) logd("Voice call ended. Handle pending updateRoamingState.");
mCT.unregisterForVoiceCallEnded(this);
// Get the current unmodified ServiceState from the tracker, as it has more info
// about the cell roaming state.
ServiceStateTracker sst = getDefaultPhone().getServiceStateTracker();
if (sst != null) {
updateRoamingState(sst.mSS);
}
break;
case EVENT_INITIATE_VOLTE_SILENT_REDIAL: {
// This is a CS -> IMS redial
if (VDBG) logd("EVENT_INITIATE_VOLTE_SILENT_REDIAL");
ar = (AsyncResult) msg.obj;
if (ar.exception == null && ar.result != null) {
SilentRedialParam result = (SilentRedialParam) ar.result;
String dialString = result.dialString;
int causeCode = result.causeCode;
DialArgs dialArgs = result.dialArgs;
if (VDBG) logd("dialString=" + dialString + " causeCode=" + causeCode);
try {
Connection cn = dial(dialString,
updateDialArgsForVolteSilentRedial(dialArgs, causeCode));
// The GSM/CDMA Connection that is owned by the GsmCdmaPhone is currently
// the one with a callback registered to TelephonyConnection. Notify the
// redial happened over that Phone so that it can be replaced with the
// new ImsPhoneConnection.
Rlog.d(LOG_TAG, "Notify volte redial connection changed cn: " + cn);
if (mDefaultPhone != null) {
// don't care it is null or not.
mDefaultPhone.notifyRedialConnectionChanged(cn);
}
} catch (CallStateException e) {
Rlog.e(LOG_TAG, "volte silent redial failed: " + e);
if (mDefaultPhone != null) {
mDefaultPhone.notifyRedialConnectionChanged(null);
}
}
} else {
if (VDBG) logd("EVENT_INITIATE_VOLTE_SILENT_REDIAL" +
" has exception or empty result");
}
break;
}
default:
super.handleMessage(msg);
break;
}
}
/**
* Listen to the IMS ECBM state change
*/
private ImsEcbmStateListener mImsEcbmStateListener =
new ImsEcbmStateListener() {
@Override
public void onECBMEntered() {
if (DBG) logd("onECBMEntered");
handleEnterEmergencyCallbackMode();
}
@Override
public void onECBMExited() {
if (DBG) logd("onECBMExited");
handleExitEmergencyCallbackMode();
}
};
@VisibleForTesting
public ImsEcbmStateListener getImsEcbmStateListener() {
return mImsEcbmStateListener;
}
@Override
public boolean isInEmergencyCall() {
return mCT.isInEmergencyCall();
}
private void sendEmergencyCallbackModeChange() {
// Send an Intent
Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, isInEcm());
SubscriptionManager.putPhoneIdAndSubIdExtra(intent, getPhoneId());
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
if (DBG) logd("sendEmergencyCallbackModeChange: isInEcm=" + isInEcm());
}
@Override
public void exitEmergencyCallbackMode() {
if (mWakeLock.isHeld()) {
mWakeLock.release();
}
if (DBG) logd("exitEmergencyCallbackMode()");
// Send a message which will invoke handleExitEmergencyCallbackMode
ImsEcbm ecbm;
try {
ecbm = mCT.getEcbmInterface();
ecbm.exitEmergencyCallbackMode();
} catch (ImsException e) {
e.printStackTrace();
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void handleEnterEmergencyCallbackMode() {
if (DBG) logd("handleEnterEmergencyCallbackMode,mIsPhoneInEcmState= " + isInEcm());
// if phone is not in Ecm mode, and it's changed to Ecm mode
if (!isInEcm()) {
setIsInEcm(true);
// notify change
sendEmergencyCallbackModeChange();
((GsmCdmaPhone) mDefaultPhone).notifyEmergencyCallRegistrants(true);
// Post this runnable so we will automatically exit
// if no one invokes exitEmergencyCallbackMode() directly.
long delayInMillis = TelephonyProperties.ecm_exit_timer()
.orElse(DEFAULT_ECM_EXIT_TIMER_VALUE);
postDelayed(mExitEcmRunnable, delayInMillis);
// We don't want to go to sleep while in Ecm
mWakeLock.acquire();
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
protected void handleExitEmergencyCallbackMode() {
if (DBG) logd("handleExitEmergencyCallbackMode: mIsPhoneInEcmState = " + isInEcm());
if (isInEcm()) {
setIsInEcm(false);
}
// Remove pending exit Ecm runnable, if any
removeCallbacks(mExitEcmRunnable);
if (mEcmExitRespRegistrant != null) {
mEcmExitRespRegistrant.notifyResult(Boolean.TRUE);
}
// release wakeLock
if (mWakeLock.isHeld()) {
mWakeLock.release();
}
// send an Intent
sendEmergencyCallbackModeChange();
((GsmCdmaPhone) mDefaultPhone).notifyEmergencyCallRegistrants(false);
}
/**
* Handle to cancel or restart Ecm timer in emergency call back mode if action is
* CANCEL_ECM_TIMER, cancel Ecm timer and notify apps the timer is canceled; otherwise, restart
* Ecm timer and notify apps the timer is restarted.
*/
void handleTimerInEmergencyCallbackMode(int action) {
switch (action) {
case CANCEL_ECM_TIMER:
removeCallbacks(mExitEcmRunnable);
((GsmCdmaPhone) mDefaultPhone).notifyEcbmTimerReset(Boolean.TRUE);
setEcmCanceledForEmergency(true /*isCanceled*/);
break;
case RESTART_ECM_TIMER:
long delayInMillis = TelephonyProperties.ecm_exit_timer()
.orElse(DEFAULT_ECM_EXIT_TIMER_VALUE);
postDelayed(mExitEcmRunnable, delayInMillis);
((GsmCdmaPhone) mDefaultPhone).notifyEcbmTimerReset(Boolean.FALSE);
setEcmCanceledForEmergency(false /*isCanceled*/);
break;
default:
loge("handleTimerInEmergencyCallbackMode, unsupported action " + action);
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public void setOnEcbModeExitResponse(Handler h, int what, Object obj) {
mEcmExitRespRegistrant = new Registrant(h, what, obj);
}
@Override
public void unsetOnEcbModeExitResponse(Handler h) {
mEcmExitRespRegistrant.clear();
}
public void onFeatureCapabilityChanged() {
mDefaultPhone.getServiceStateTracker().onImsCapabilityChanged();
}
@Override
public boolean isImsCapabilityAvailable(int capability, int regTech) throws ImsException {
return mCT.isImsCapabilityAvailable(capability, regTech);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public boolean isVolteEnabled() {
return isVoiceOverCellularImsEnabled();
}
@Override
public boolean isVoiceOverCellularImsEnabled() {
return mCT.isVoiceOverCellularImsEnabled();
}
@Override
public boolean isWifiCallingEnabled() {
return mCT.isVowifiEnabled();
}
@Override
public boolean isVideoEnabled() {
return mCT.isVideoCallEnabled();
}
@Override
public int getImsRegistrationTech() {
return mCT.getImsRegistrationTech();
}
@Override
public void getImsRegistrationTech(Consumer<Integer> callback) {
mCT.getImsRegistrationTech(callback);
}
@Override
public void getImsRegistrationState(Consumer<Integer> callback) {
callback.accept(mImsMmTelRegistrationHelper.getImsRegistrationState());
}
@Override
public Phone getDefaultPhone() {
return mDefaultPhone;
}
@Override
public boolean isImsRegistered() {
return mImsMmTelRegistrationHelper.isImsRegistered();
}
// Not used, but not removed due to UnsupportedAppUsage tag.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void setImsRegistered(boolean isRegistered) {
mImsMmTelRegistrationHelper.updateRegistrationState(
isRegistered ? RegistrationManager.REGISTRATION_STATE_REGISTERED :
RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
}
@Override
public void callEndCleanupHandOverCallIfAny() {
mCT.callEndCleanupHandOverCallIfAny();
}
private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Add notification only if alert was not shown by WfcSettings
if (getResultCode() == Activity.RESULT_OK) {
// Default result code (as passed to sendOrderedBroadcast)
// means that intent was not received by WfcSettings.
CharSequence title =
intent.getCharSequenceExtra(EXTRA_WFC_REGISTRATION_FAILURE_TITLE);
CharSequence messageAlert =
intent.getCharSequenceExtra(EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE);
CharSequence messageNotification =
intent.getCharSequenceExtra(EXTRA_KEY_NOTIFICATION_MESSAGE);
Intent resultIntent = new Intent(Intent.ACTION_MAIN);
// Note: If the classname below is ever removed, the call to
// PendingIntent.getActivity should also specify FLAG_IMMUTABLE to ensure the
// pending intent cannot be tampered with.
resultIntent.setClassName("com.android.settings",
"com.android.settings.Settings$WifiCallingSettingsActivity");
resultIntent.putExtra(EXTRA_KEY_ALERT_SHOW, true);
resultIntent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_TITLE, title);
resultIntent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE, messageAlert);
PendingIntent resultPendingIntent =
PendingIntent.getActivity(
mContext,
0,
resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
final Notification notification = new Notification.Builder(mContext)
.setSmallIcon(android.R.drawable.stat_sys_warning)
.setContentTitle(title)
.setContentText(messageNotification)
.setAutoCancel(true)
.setContentIntent(resultPendingIntent)
.setStyle(new Notification.BigTextStyle()
.bigText(messageNotification))
.setChannelId(NotificationChannelController.CHANNEL_ID_WFC)
.build();
final String notificationTag = "wifi_calling";
final int notificationId = 1;
NotificationManager notificationManager =
(NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
notificationManager.notify(notificationTag, notificationId,
notification);
}
}
};
/**
* Show notification in case of some error codes.
*/
public void processDisconnectReason(ImsReasonInfo imsReasonInfo) {
if (imsReasonInfo.mCode == imsReasonInfo.CODE_REGISTRATION_ERROR
&& imsReasonInfo.mExtraMessage != null) {
// Suppress WFC Registration notifications if WFC is not enabled by the user.
if (mImsManagerFactory.create(mContext, mPhoneId).isWfcEnabledByUser()) {
processWfcDisconnectForNotification(imsReasonInfo);
}
}
}
// Processes an IMS disconnect cause for possible WFC registration errors and optionally
// disable WFC.
private void processWfcDisconnectForNotification(ImsReasonInfo imsReasonInfo) {
CarrierConfigManager configManager =
(CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configManager == null) {
loge("processDisconnectReason: CarrierConfigManager is not ready");
return;
}
PersistableBundle pb = configManager.getConfigForSubId(getSubId());
if (pb == null) {
loge("processDisconnectReason: no config for subId " + getSubId());
return;
}
final String[] wfcOperatorErrorCodes =
pb.getStringArray(
CarrierConfigManager.KEY_WFC_OPERATOR_ERROR_CODES_STRING_ARRAY);
if (wfcOperatorErrorCodes == null) {
// no operator-specific error codes
return;
}
final String[] wfcOperatorErrorAlertMessages =
mContext.getResources().getStringArray(
com.android.internal.R.array.wfcOperatorErrorAlertMessages);
final String[] wfcOperatorErrorNotificationMessages =
mContext.getResources().getStringArray(
com.android.internal.R.array.wfcOperatorErrorNotificationMessages);
for (int i = 0; i < wfcOperatorErrorCodes.length; i++) {
String[] codes = wfcOperatorErrorCodes[i].split("\\|");
if (codes.length != 2) {
loge("Invalid carrier config: " + wfcOperatorErrorCodes[i]);
continue;
}
// Match error code.
if (!imsReasonInfo.mExtraMessage.startsWith(
codes[0])) {
continue;
}
// If there is no delimiter at the end of error code string
// then we need to verify that we are not matching partial code.
// EXAMPLE: "REG9" must not match "REG99".
// NOTE: Error code must not be empty.
int codeStringLength = codes[0].length();
char lastChar = codes[0].charAt(codeStringLength - 1);
if (Character.isLetterOrDigit(lastChar)) {
if (imsReasonInfo.mExtraMessage.length() > codeStringLength) {
char nextChar = imsReasonInfo.mExtraMessage.charAt(codeStringLength);
if (Character.isLetterOrDigit(nextChar)) {
continue;
}
}
}
final CharSequence title = mContext.getText(
com.android.internal.R.string.wfcRegErrorTitle);
int idx = Integer.parseInt(codes[1]);
if (idx < 0
|| idx >= wfcOperatorErrorAlertMessages.length
|| idx >= wfcOperatorErrorNotificationMessages.length) {
loge("Invalid index: " + wfcOperatorErrorCodes[i]);
continue;
}
String messageAlert = imsReasonInfo.mExtraMessage;
String messageNotification = imsReasonInfo.mExtraMessage;
if (!wfcOperatorErrorAlertMessages[idx].isEmpty()) {
messageAlert = String.format(
wfcOperatorErrorAlertMessages[idx],
imsReasonInfo.mExtraMessage); // Fill IMS error code into alert message
}
if (!wfcOperatorErrorNotificationMessages[idx].isEmpty()) {
messageNotification = String.format(
wfcOperatorErrorNotificationMessages[idx],
imsReasonInfo.mExtraMessage); // Fill IMS error code into notification
}
// If WfcSettings are active then alert will be shown
// otherwise notification will be added.
Intent intent = new Intent(
android.telephony.ims.ImsManager.ACTION_WFC_IMS_REGISTRATION_ERROR);
intent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_TITLE, title);
intent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE, messageAlert);
intent.putExtra(EXTRA_KEY_NOTIFICATION_MESSAGE, messageNotification);
mContext.sendOrderedBroadcast(intent, null, mResultReceiver,
null, Activity.RESULT_OK, null, null);
// We can only match a single error code
// so should break the loop after a successful match.
break;
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Override
public boolean isUtEnabled() {
return mCT.isUtEnabled();
}
@Override
public void sendEmergencyCallStateChange(boolean callActive) {
mDefaultPhone.sendEmergencyCallStateChange(callActive);
}
@Override
public void setBroadcastEmergencyCallStateChanges(boolean broadcast) {
mDefaultPhone.setBroadcastEmergencyCallStateChanges(broadcast);
}
@VisibleForTesting
public PowerManager.WakeLock getWakeLock() {
return mWakeLock;
}
/**
* Update roaming state and WFC mode in the following situations:
* 1) voice is in service.
* 2) data is in service and it is not IWLAN (if in legacy mode).
* @param ss non-null ServiceState
*/
private void updateRoamingState(ServiceState ss) {
if (ss == null) {
loge("updateRoamingState: null ServiceState!");
return;
}
boolean newRoamingState = ss.getRoaming();
// Do not recalculate if there is no change to state.
if (mLastKnownRoamingState == newRoamingState) {
return;
}
boolean isInService = (ss.getState() == ServiceState.STATE_IN_SERVICE
|| ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE);
// If we are not IN_SERVICE for voice or data, ignore change roaming state, as we always
// move to home in this case.
if (!isInService || !mDefaultPhone.isRadioOn()) {
logi("updateRoamingState: we are not IN_SERVICE, ignoring roaming change.");
return;
}
// We ignore roaming changes when moving to IWLAN because it always sets the roaming
// mode to home and masks the actual cellular roaming status if voice is not registered. If
// we just moved to IWLAN because WFC roaming mode is IWLAN preferred and WFC home mode is
// cell preferred, we can get into a condition where the modem keeps bouncing between
// IWLAN->cell->IWLAN->cell...
if (isCsNotInServiceAndPsWwanReportingWlan(ss)) {
logi("updateRoamingState: IWLAN masking roaming, ignore roaming change.");
return;
}
if (mCT.getState() == PhoneConstants.State.IDLE) {
if (DBG) logd("updateRoamingState now: " + newRoamingState);
mLastKnownRoamingState = newRoamingState;
CarrierConfigManager configManager = (CarrierConfigManager)
getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
// Don't set wfc mode if carrierconfig has not loaded. It will be set by GsmCdmaPhone
// when receives ACTION_CARRIER_CONFIG_CHANGED broadcast.
if (configManager != null && CarrierConfigManager.isConfigForIdentifiedCarrier(
configManager.getConfigForSubId(getSubId()))) {
ImsManager imsManager = mImsManagerFactory.create(mContext, mPhoneId);
imsManager.setWfcMode(imsManager.getWfcMode(newRoamingState), newRoamingState);
}
} else {
if (DBG) logd("updateRoamingState postponed: " + newRoamingState);
mCT.registerForVoiceCallEnded(this, EVENT_VOICE_CALL_ENDED, null);
}
}
/**
* In legacy mode, data registration will report IWLAN when we are using WLAN for data,
* effectively masking the true roaming state of the device if voice is not registered.
*
* @return true if we are reporting not in service for CS domain over WWAN transport and WLAN
* for PS domain over WWAN transport.
*/
private boolean isCsNotInServiceAndPsWwanReportingWlan(ServiceState ss) {
TransportManager tm = mDefaultPhone.getTransportManager();
// We can not get into this condition if we are in AP-Assisted mode.
if (tm == null || !tm.isInLegacyMode()) {
return false;
}
NetworkRegistrationInfo csInfo = ss.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
NetworkRegistrationInfo psInfo = ss.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
// We will return roaming state correctly if the CS domain is in service because
// ss.getRoaming() returns isVoiceRoaming||isDataRoaming result and isDataRoaming==false
// when the modem reports IWLAN RAT.
return psInfo != null && csInfo != null && !csInfo.isInService()
&& psInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_IWLAN;
}
public RegistrationManager.RegistrationCallback getImsMmTelRegistrationCallback() {
return mImsMmTelRegistrationHelper.getCallback();
}
/**
* Reset the IMS registration state.
*/
public void resetImsRegistrationState() {
if (DBG) logd("resetImsRegistrationState");
mImsMmTelRegistrationHelper.reset();
}
private ImsRegistrationCallbackHelper.ImsRegistrationUpdate mMmTelRegistrationUpdate = new
ImsRegistrationCallbackHelper.ImsRegistrationUpdate() {
@Override
public void handleImsRegistered(int imsRadioTech) {
if (DBG) {
logd("handleImsRegistered: onImsMmTelConnected imsRadioTech="
+ AccessNetworkConstants.transportTypeToString(imsRadioTech));
}
mRegLocalLog.log("handleImsRegistered: onImsMmTelConnected imsRadioTech="
+ AccessNetworkConstants.transportTypeToString(imsRadioTech));
setServiceState(ServiceState.STATE_IN_SERVICE);
getDefaultPhone().setImsRegistrationState(true);
mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.CONNECTED, null);
mImsStats.onImsRegistered(imsRadioTech);
}
@Override
public void handleImsRegistering(int imsRadioTech) {
if (DBG) {
logd("handleImsRegistering: onImsMmTelProgressing imsRadioTech="
+ AccessNetworkConstants.transportTypeToString(imsRadioTech));
}
mRegLocalLog.log("handleImsRegistering: onImsMmTelProgressing imsRadioTech="
+ AccessNetworkConstants.transportTypeToString(imsRadioTech));
setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
getDefaultPhone().setImsRegistrationState(false);
mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.PROGRESSING,
null);
mImsStats.onImsRegistering(imsRadioTech);
}
@Override
public void handleImsUnregistered(ImsReasonInfo imsReasonInfo) {
if (DBG) {
logd("handleImsUnregistered: onImsMmTelDisconnected imsReasonInfo="
+ imsReasonInfo);
}
mRegLocalLog.log("handleImsUnregistered: onImsMmTelDisconnected imsRadioTech="
+ imsReasonInfo);
setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
processDisconnectReason(imsReasonInfo);
getDefaultPhone().setImsRegistrationState(false);
mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.DISCONNECTED,
imsReasonInfo);
mImsStats.onImsUnregistered(imsReasonInfo);
}
@Override
public void handleImsSubscriberAssociatedUriChanged(Uri[] uris) {
if (DBG) logd("handleImsSubscriberAssociatedUriChanged");
setCurrentSubscriberUris(uris);
}
};
public IccRecords getIccRecords() {
return mDefaultPhone.getIccRecords();
}
public DialArgs updateDialArgsForVolteSilentRedial(DialArgs dialArgs, int causeCode) {
if (dialArgs != null) {
ImsPhone.ImsDialArgs.Builder imsDialArgsBuilder;
imsDialArgsBuilder = ImsPhone.ImsDialArgs.Builder.from(dialArgs);
Bundle extras = new Bundle(dialArgs.intentExtras);
if (causeCode == CallFailCause.EMC_REDIAL_ON_VOWIFI) {
extras.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
String.valueOf(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN));
logd("trigger VoWifi emergency call");
imsDialArgsBuilder.setIntentExtras(extras);
} else if (causeCode == CallFailCause.EMC_REDIAL_ON_IMS) {
logd("trigger VoLte emergency call");
}
return imsDialArgsBuilder.build();
}
return new DialArgs.Builder<>().build();
}
@Override
public VoiceCallSessionStats getVoiceCallSessionStats() {
return mDefaultPhone.getVoiceCallSessionStats();
}
/** Returns the {@link ImsStats} for this IMS phone. */
public ImsStats getImsStats() {
return mImsStats;
}
/** Sets the {@link ImsStats} mock for this IMS phone during unit testing. */
@VisibleForTesting
public void setImsStats(ImsStats imsStats) {
mImsStats = imsStats;
}
public boolean hasAliveCall() {
return (getForegroundCall().getState() != Call.State.IDLE ||
getBackgroundCall().getState() != Call.State.IDLE);
}
public boolean getLastKnownRoamingState() {
return mLastKnownRoamingState;
}
@Override
public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println("ImsPhone extends:");
super.dump(fd, pw, args);
pw.flush();
pw.println("ImsPhone:");
pw.println(" mDefaultPhone = " + mDefaultPhone);
pw.println(" mPendingMMIs = " + mPendingMMIs);
pw.println(" mPostDialHandler = " + mPostDialHandler);
pw.println(" mSS = " + mSS);
pw.println(" mWakeLock = " + mWakeLock);
pw.println(" mIsPhoneInEcmState = " + isInEcm());
pw.println(" mEcmExitRespRegistrant = " + mEcmExitRespRegistrant);
pw.println(" mSilentRedialRegistrants = " + mSilentRedialRegistrants);
pw.println(" mImsMmTelRegistrationState = "
+ mImsMmTelRegistrationHelper.getImsRegistrationState());
pw.println(" mLastKnownRoamingState = " + mLastKnownRoamingState);
pw.println(" mSsnRegistrants = " + mSsnRegistrants);
pw.println(" Registration Log:");
pw.increaseIndent();
mRegLocalLog.dump(pw);
pw.decreaseIndent();
pw.flush();
}
private void logi(String s) {
Rlog.i(LOG_TAG, "[" + mPhoneId + "] " + s);
}
private void logv(String s) {
Rlog.v(LOG_TAG, "[" + mPhoneId + "] " + s);
}
private void logd(String s) {
Rlog.d(LOG_TAG, "[" + mPhoneId + "] " + s);
}
private void loge(String s) {
Rlog.e(LOG_TAG, "[" + mPhoneId + "] " + s);
}
}