blob: 1525ff7ca8206fea564e00a87b5075bb9f236887 [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.ServiceState.STATE_IN_SERVICE;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA_ASYNC;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA_SYNC;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_FAX;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_MAX;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_PACKET;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_PAD;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_SMS;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ResultReceiver;
import android.telephony.PhoneNumberUtils;
import android.telephony.Rlog;
import android.telephony.ims.ImsCallForwardInfo;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsSsData;
import android.telephony.ims.ImsSsInfo;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import com.android.ims.ImsException;
import com.android.ims.ImsUtInterface;
import com.android.internal.telephony.CallForwardInfo;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.MmiCode;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.uicc.IccRecords;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The motto for this file is:
*
* "NOTE: By using the # as a separator, most cases are expected to be unambiguous."
* -- TS 22.030 6.5.2
*
* {@hide}
*
*/
public final class ImsPhoneMmiCode extends Handler implements MmiCode {
static final String LOG_TAG = "ImsPhoneMmiCode";
//***** Constants
// Max Size of the Short Code (aka Short String from TS 22.030 6.5.2)
private static final int MAX_LENGTH_SHORT_CODE = 2;
// TS 22.030 6.5.2 Every Short String USSD command will end with #-key
// (known as #-String)
private static final char END_OF_USSD_COMMAND = '#';
// From TS 22.030 6.5.2
private static final String ACTION_ACTIVATE = "*";
private static final String ACTION_DEACTIVATE = "#";
private static final String ACTION_INTERROGATE = "*#";
private static final String ACTION_REGISTER = "**";
private static final String ACTION_ERASURE = "##";
// Supp Service codes from TS 22.030 Annex B
//Called line presentation
private static final String SC_CLIP = "30";
private static final String SC_CLIR = "31";
private static final String SC_COLP = "76";
private static final String SC_COLR = "77";
//Calling name presentation
private static final String SC_CNAP = "300";
// Call Forwarding
private static final String SC_CFU = "21";
private static final String SC_CFB = "67";
private static final String SC_CFNRy = "61";
private static final String SC_CFNR = "62";
// Call Forwarding unconditional Timer
private static final String SC_CFUT = "22";
private static final String SC_CF_All = "002";
private static final String SC_CF_All_Conditional = "004";
// Call Waiting
private static final String SC_WAIT = "43";
// Call Barring
private static final String SC_BAOC = "33";
private static final String SC_BAOIC = "331";
private static final String SC_BAOICxH = "332";
private static final String SC_BAIC = "35";
private static final String SC_BAICr = "351";
private static final String SC_BA_ALL = "330";
private static final String SC_BA_MO = "333";
private static final String SC_BA_MT = "353";
// Incoming/Anonymous call barring
private static final String SC_BS_MT = "156";
private static final String SC_BAICa = "157";
// Supp Service Password registration
private static final String SC_PWD = "03";
// PIN/PIN2/PUK/PUK2
private static final String SC_PIN = "04";
private static final String SC_PIN2 = "042";
private static final String SC_PUK = "05";
private static final String SC_PUK2 = "052";
//***** Event Constants
private static final int EVENT_SET_COMPLETE = 0;
private static final int EVENT_QUERY_CF_COMPLETE = 1;
private static final int EVENT_USSD_COMPLETE = 2;
private static final int EVENT_QUERY_COMPLETE = 3;
private static final int EVENT_SET_CFF_COMPLETE = 4;
private static final int EVENT_USSD_CANCEL_COMPLETE = 5;
private static final int EVENT_GET_CLIR_COMPLETE = 6;
private static final int EVENT_SUPP_SVC_QUERY_COMPLETE = 7;
private static final int EVENT_QUERY_ICB_COMPLETE = 10;
//***** Calling Line Presentation Constants
private static final int NUM_PRESENTATION_ALLOWED = 0;
private static final int NUM_PRESENTATION_RESTRICTED = 1;
//***** Supplementary Service Query Bundle Keys
// Used by IMS Service layer to put supp. serv. query
// responses into the ssInfo Bundle.
public static final String UT_BUNDLE_KEY_CLIR = "queryClir";
public static final String UT_BUNDLE_KEY_SSINFO = "imsSsInfo";
//***** Instance Variables
@UnsupportedAppUsage
private ImsPhone mPhone;
@UnsupportedAppUsage
private Context mContext;
private IccRecords mIccRecords;
private String mAction; // One of ACTION_*
private String mSc; // Service Code
private String mSia, mSib, mSic; // Service Info a,b,c
private String mPoundString; // Entire MMI string up to and including #
private String mDialingNumber;
private String mPwd; // For password registration
private ResultReceiver mCallbackReceiver;
private boolean mIsPendingUSSD;
private boolean mIsUssdRequest;
private boolean mIsCallFwdReg;
private State mState = State.PENDING;
private CharSequence mMessage;
private boolean mIsSsInfo = false;
//resgister/erasure of ICB (Specific DN)
static final String IcbDnMmi = "Specific Incoming Call Barring";
//ICB (Anonymous)
static final String IcbAnonymousMmi = "Anonymous Incoming Call Barring";
//***** Class Variables
// See TS 22.030 6.5.2 "Structure of the MMI"
private static Pattern sPatternSuppService = Pattern.compile(
"((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
/* 1 2 3 4 5 6 7 8 9 10 11 12
1 = Full string up to and including #
2 = action (activation/interrogation/registration/erasure)
3 = service code
5 = SIA
7 = SIB
9 = SIC
10 = dialing number
*/
private static final int MATCH_GROUP_POUND_STRING = 1;
private static final int MATCH_GROUP_ACTION = 2;
//(activation/interrogation/registration/erasure)
private static final int MATCH_GROUP_SERVICE_CODE = 3;
private static final int MATCH_GROUP_SIA = 5;
private static final int MATCH_GROUP_SIB = 7;
private static final int MATCH_GROUP_SIC = 9;
private static final int MATCH_GROUP_PWD_CONFIRM = 11;
private static final int MATCH_GROUP_DIALING_NUMBER = 12;
static private String[] sTwoDigitNumberPattern;
//***** Public Class methods
/**
* Some dial strings in GSM are defined to do non-call setup
* things, such as modify or query supplementary service settings (eg, call
* forwarding). These are generally referred to as "MMI codes".
* We look to see if the dial string contains a valid MMI code (potentially
* with a dial string at the end as well) and return info here.
*
* If the dial string contains no MMI code, we return an instance with
* only "dialingNumber" set
*
* Please see flow chart in TS 22.030 6.5.3.2
*/
@UnsupportedAppUsage
static ImsPhoneMmiCode newFromDialString(String dialString, ImsPhone phone) {
return newFromDialString(dialString, phone, null);
}
static ImsPhoneMmiCode newFromDialString(String dialString,
ImsPhone phone, ResultReceiver wrappedCallback) {
Matcher m;
ImsPhoneMmiCode ret = null;
if (phone.getDefaultPhone().getServiceState().getVoiceRoaming()
&& phone.getDefaultPhone().supportsConversionOfCdmaCallerIdMmiCodesWhileRoaming()) {
/* The CDMA MMI coded dialString will be converted to a 3GPP MMI Coded dialString
so that it can be processed by the matcher and code below
*/
dialString = convertCdmaMmiCodesTo3gppMmiCodes(dialString);
}
m = sPatternSuppService.matcher(dialString);
// Is this formatted like a standard supplementary service code?
if (m.matches()) {
ret = new ImsPhoneMmiCode(phone);
ret.mPoundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING));
ret.mAction = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
ret.mSc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
ret.mSia = makeEmptyNull(m.group(MATCH_GROUP_SIA));
ret.mSib = makeEmptyNull(m.group(MATCH_GROUP_SIB));
ret.mSic = makeEmptyNull(m.group(MATCH_GROUP_SIC));
ret.mPwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM));
ret.mDialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER));
ret.mCallbackReceiver = wrappedCallback;
// According to TS 22.030 6.5.2 "Structure of the MMI",
// the dialing number should not ending with #.
// The dialing number ending # is treated as unique USSD,
// eg, *400#16 digit number# to recharge the prepaid card
// in India operator(Mumbai MTNL)
if (ret.mDialingNumber != null &&
ret.mDialingNumber.endsWith("#") &&
dialString.endsWith("#")){
ret = new ImsPhoneMmiCode(phone);
ret.mPoundString = dialString;
}
} else if (dialString.endsWith("#")) {
// TS 22.030 sec 6.5.3.2
// "Entry of any characters defined in the 3GPP TS 23.038 [8] Default Alphabet
// (up to the maximum defined in 3GPP TS 24.080 [10]), followed by #SEND".
ret = new ImsPhoneMmiCode(phone);
ret.mPoundString = dialString;
} else if (isTwoDigitShortCode(phone.getContext(), dialString)) {
//Is a country-specific exception to short codes as defined in TS 22.030, 6.5.3.2
ret = null;
} else if (isShortCode(dialString, phone)) {
// this may be a short code, as defined in TS 22.030, 6.5.3.2
ret = new ImsPhoneMmiCode(phone);
ret.mDialingNumber = dialString;
}
return ret;
}
private static String convertCdmaMmiCodesTo3gppMmiCodes(String dialString) {
Matcher m;
m = sPatternCdmaMmiCodeWhileRoaming.matcher(dialString);
if (m.matches()) {
String serviceCode = makeEmptyNull(m.group(MATCH_GROUP_CDMA_MMI_CODE_SERVICE_CODE));
String prefix = m.group(MATCH_GROUP_CDMA_MMI_CODE_NUMBER_PREFIX);
String number = makeEmptyNull(m.group(MATCH_GROUP_CDMA_MMI_CODE_NUMBER));
if (serviceCode.equals("67") && number != null) {
// "#31#number" to invoke CLIR
dialString = ACTION_DEACTIVATE + SC_CLIR + ACTION_DEACTIVATE + prefix + number;
} else if (serviceCode.equals("82") && number != null) {
// "*31#number" to suppress CLIR
dialString = ACTION_ACTIVATE + SC_CLIR + ACTION_DEACTIVATE + prefix + number;
}
}
return dialString;
}
static ImsPhoneMmiCode
newNetworkInitiatedUssd(String ussdMessage, boolean isUssdRequest, ImsPhone phone) {
ImsPhoneMmiCode ret;
ret = new ImsPhoneMmiCode(phone);
ret.mMessage = ussdMessage;
ret.mIsUssdRequest = isUssdRequest;
// If it's a request, set to PENDING so that it's cancelable.
if (isUssdRequest) {
ret.mIsPendingUSSD = true;
ret.mState = State.PENDING;
} else {
ret.mState = State.COMPLETE;
}
return ret;
}
static ImsPhoneMmiCode newFromUssdUserInput(String ussdMessge, ImsPhone phone) {
ImsPhoneMmiCode ret = new ImsPhoneMmiCode(phone);
ret.mMessage = ussdMessge;
ret.mState = State.PENDING;
ret.mIsPendingUSSD = true;
return ret;
}
//***** Private Class methods
/** make empty strings be null.
* Regexp returns empty strings for empty groups
*/
private static String
makeEmptyNull (String s) {
if (s != null && s.length() == 0) return null;
return s;
}
static boolean isScMatchesSuppServType(String dialString) {
boolean isMatch = false;
Matcher m = sPatternSuppService.matcher(dialString);
if (m.matches()) {
String sc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
if (sc.equals(SC_CFUT)) {
isMatch = true;
} else if(sc.equals(SC_BS_MT)) {
isMatch = true;
}
}
return isMatch;
}
/** returns true of the string is empty or null */
@UnsupportedAppUsage
private static boolean
isEmptyOrNull(CharSequence s) {
return s == null || (s.length() == 0);
}
private static int
scToCallForwardReason(String sc) {
if (sc == null) {
throw new RuntimeException ("invalid call forward sc");
}
if (sc.equals(SC_CF_All)) {
return CommandsInterface.CF_REASON_ALL;
} else if (sc.equals(SC_CFU)) {
return CommandsInterface.CF_REASON_UNCONDITIONAL;
} else if (sc.equals(SC_CFB)) {
return CommandsInterface.CF_REASON_BUSY;
} else if (sc.equals(SC_CFNR)) {
return CommandsInterface.CF_REASON_NOT_REACHABLE;
} else if (sc.equals(SC_CFNRy)) {
return CommandsInterface.CF_REASON_NO_REPLY;
} else if (sc.equals(SC_CF_All_Conditional)) {
return CommandsInterface.CF_REASON_ALL_CONDITIONAL;
} else {
throw new RuntimeException ("invalid call forward sc");
}
}
private static int
siToServiceClass(String si) {
if (si == null || si.length() == 0) {
return SERVICE_CLASS_NONE;
} else {
// NumberFormatException should cause MMI fail
int serviceCode = Integer.parseInt(si, 10);
switch (serviceCode) {
case 10: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX + SERVICE_CLASS_VOICE;
case 11: return SERVICE_CLASS_VOICE;
case 12: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX;
case 13: return SERVICE_CLASS_FAX;
case 16: return SERVICE_CLASS_SMS;
case 19: return SERVICE_CLASS_FAX + SERVICE_CLASS_VOICE;
case 20: return SERVICE_CLASS_DATA_ASYNC + SERVICE_CLASS_DATA_SYNC;
case 21: return SERVICE_CLASS_PAD + SERVICE_CLASS_DATA_ASYNC;
case 22: return SERVICE_CLASS_PACKET + SERVICE_CLASS_DATA_SYNC;
case 24: return SERVICE_CLASS_DATA_SYNC;
case 25: return SERVICE_CLASS_DATA_ASYNC;
case 26: return SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE;
case 99: return SERVICE_CLASS_PACKET;
default:
throw new RuntimeException("unsupported MMI service code " + si);
}
}
}
private static int
siToTime (String si) {
if (si == null || si.length() == 0) {
return 0;
} else {
// NumberFormatException should cause MMI fail
return Integer.parseInt(si, 10);
}
}
static boolean
isServiceCodeCallForwarding(String sc) {
return sc != null &&
(sc.equals(SC_CFU)
|| sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
|| sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
|| sc.equals(SC_CF_All_Conditional));
}
static boolean
isServiceCodeCallBarring(String sc) {
Resources resource = Resources.getSystem();
if (sc != null) {
String[] barringMMI = resource.getStringArray(
com.android.internal.R.array.config_callBarringMMI);
if (barringMMI != null) {
for (String match : barringMMI) {
if (sc.equals(match)) return true;
}
}
}
return false;
}
static String
scToBarringFacility(String sc) {
if (sc == null) {
throw new RuntimeException ("invalid call barring sc");
}
if (sc.equals(SC_BAOC)) {
return CommandsInterface.CB_FACILITY_BAOC;
} else if (sc.equals(SC_BAOIC)) {
return CommandsInterface.CB_FACILITY_BAOIC;
} else if (sc.equals(SC_BAOICxH)) {
return CommandsInterface.CB_FACILITY_BAOICxH;
} else if (sc.equals(SC_BAIC)) {
return CommandsInterface.CB_FACILITY_BAIC;
} else if (sc.equals(SC_BAICr)) {
return CommandsInterface.CB_FACILITY_BAICr;
} else if (sc.equals(SC_BA_ALL)) {
return CommandsInterface.CB_FACILITY_BA_ALL;
} else if (sc.equals(SC_BA_MO)) {
return CommandsInterface.CB_FACILITY_BA_MO;
} else if (sc.equals(SC_BA_MT)) {
return CommandsInterface.CB_FACILITY_BA_MT;
} else {
throw new RuntimeException ("invalid call barring sc");
}
}
//***** Constructor
public ImsPhoneMmiCode(ImsPhone phone) {
// The telephony unit-test cases may create ImsPhoneMmiCode's
// in secondary threads
super(phone.getHandler().getLooper());
mPhone = phone;
mContext = phone.getContext();
mIccRecords = mPhone.mDefaultPhone.getIccRecords();
}
//***** MmiCode implementation
@Override
public State
getState() {
return mState;
}
@Override
public CharSequence
getMessage() {
return mMessage;
}
@Override
public Phone getPhone() { return mPhone; }
// inherited javadoc suffices
@Override
public void
cancel() {
// Complete or failed cannot be cancelled
if (mState == State.COMPLETE || mState == State.FAILED) {
return;
}
mState = State.CANCELLED;
if (mIsPendingUSSD) {
mPhone.cancelUSSD(obtainMessage(EVENT_USSD_CANCEL_COMPLETE, this));
} else {
mPhone.onMMIDone (this);
}
}
@Override
public boolean isCancelable() {
/* Can only cancel pending USSD sessions. */
return mIsPendingUSSD;
}
//***** Instance Methods
@UnsupportedAppUsage
String getDialingNumber() {
return mDialingNumber;
}
/** Does this dial string contain a structured or unstructured MMI code? */
boolean
isMMI() {
return mPoundString != null;
}
/* Is this a 1 or 2 digit "short code" as defined in TS 22.030 sec 6.5.3.2? */
boolean
isShortCode() {
return mPoundString == null
&& mDialingNumber != null && mDialingNumber.length() <= 2;
}
@Override
public String getDialString() {
return mPoundString;
}
static private boolean
isTwoDigitShortCode(Context context, String dialString) {
Rlog.d(LOG_TAG, "isTwoDigitShortCode");
if (dialString == null || dialString.length() > 2) return false;
if (sTwoDigitNumberPattern == null) {
sTwoDigitNumberPattern = context.getResources().getStringArray(
com.android.internal.R.array.config_twoDigitNumberPattern);
}
for (String dialnumber : sTwoDigitNumberPattern) {
Rlog.d(LOG_TAG, "Two Digit Number Pattern " + dialnumber);
if (dialString.equals(dialnumber)) {
Rlog.d(LOG_TAG, "Two Digit Number Pattern -true");
return true;
}
}
Rlog.d(LOG_TAG, "Two Digit Number Pattern -false");
return false;
}
/**
* Helper function for newFromDialString. Returns true if dialString appears
* to be a short code AND conditions are correct for it to be treated as
* such.
*/
static private boolean isShortCode(String dialString, ImsPhone phone) {
// Refer to TS 22.030 Figure 3.5.3.2:
if (dialString == null) {
return false;
}
// Illegal dial string characters will give a ZERO length.
// At this point we do not want to crash as any application with
// call privileges may send a non dial string.
// It return false as when the dialString is equal to NULL.
if (dialString.length() == 0) {
return false;
}
if (PhoneNumberUtils.isLocalEmergencyNumber(phone.getContext(), dialString)) {
return false;
} else {
return isShortCodeUSSD(dialString, phone);
}
}
/**
* Helper function for isShortCode. Returns true if dialString appears to be
* a short code and it is a USSD structure
*
* According to the 3PGG TS 22.030 specification Figure 3.5.3.2: A 1 or 2
* digit "short code" is treated as USSD if it is entered while on a call or
* does not satisfy the condition (exactly 2 digits && starts with '1'), there
* are however exceptions to this rule (see below)
*
* Exception (1) to Call initiation is: If the user of the device is already in a call
* and enters a Short String without any #-key at the end and the length of the Short String is
* equal or less then the MAX_LENGTH_SHORT_CODE [constant that is equal to 2]
*
* The phone shall initiate a USSD/SS commands.
*/
static private boolean isShortCodeUSSD(String dialString, ImsPhone phone) {
if (dialString != null && dialString.length() <= MAX_LENGTH_SHORT_CODE) {
if (phone.isInCall()) {
return true;
}
if (dialString.length() != MAX_LENGTH_SHORT_CODE ||
dialString.charAt(0) != '1') {
return true;
}
}
return false;
}
/**
* @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
*/
public boolean isPinPukCommand() {
return mSc != null && (mSc.equals(SC_PIN) || mSc.equals(SC_PIN2)
|| mSc.equals(SC_PUK) || mSc.equals(SC_PUK2));
}
/**
* See TS 22.030 Annex B.
* In temporary mode, to suppress CLIR for a single call, enter:
* " * 31 # [called number] SEND "
* In temporary mode, to invoke CLIR for a single call enter:
* " # 31 # [called number] SEND "
*/
@UnsupportedAppUsage
boolean
isTemporaryModeCLIR() {
return mSc != null && mSc.equals(SC_CLIR) && mDialingNumber != null
&& (isActivate() || isDeactivate());
}
/**
* returns CommandsInterface.CLIR_*
* See also isTemporaryModeCLIR()
*/
@UnsupportedAppUsage
int
getCLIRMode() {
if (mSc != null && mSc.equals(SC_CLIR)) {
if (isActivate()) {
return CommandsInterface.CLIR_SUPPRESSION;
} else if (isDeactivate()) {
return CommandsInterface.CLIR_INVOCATION;
}
}
return CommandsInterface.CLIR_DEFAULT;
}
@UnsupportedAppUsage
boolean isActivate() {
return mAction != null && mAction.equals(ACTION_ACTIVATE);
}
@UnsupportedAppUsage
boolean isDeactivate() {
return mAction != null && mAction.equals(ACTION_DEACTIVATE);
}
boolean isInterrogate() {
return mAction != null && mAction.equals(ACTION_INTERROGATE);
}
@UnsupportedAppUsage
boolean isRegister() {
return mAction != null && mAction.equals(ACTION_REGISTER);
}
@UnsupportedAppUsage
boolean isErasure() {
return mAction != null && mAction.equals(ACTION_ERASURE);
}
/**
* Returns true if this is a USSD code that's been submitted to the
* network...eg, after processCode() is called
*/
public boolean isPendingUSSD() {
return mIsPendingUSSD;
}
@Override
public boolean isUssdRequest() {
return mIsUssdRequest;
}
@UnsupportedAppUsage
boolean
isSupportedOverImsPhone() {
if (isShortCode()) return true;
else if (isServiceCodeCallForwarding(mSc)
|| isServiceCodeCallBarring(mSc)
|| (mSc != null && mSc.equals(SC_WAIT))
|| (mSc != null && mSc.equals(SC_CLIR))
|| (mSc != null && mSc.equals(SC_CLIP))
|| (mSc != null && mSc.equals(SC_COLR))
|| (mSc != null && mSc.equals(SC_COLP))
|| (mSc != null && mSc.equals(SC_BS_MT))
|| (mSc != null && mSc.equals(SC_BAICa))) {
try {
int serviceClass = siToServiceClass(mSib);
if (serviceClass != SERVICE_CLASS_NONE
&& serviceClass != SERVICE_CLASS_VOICE
&& serviceClass != (SERVICE_CLASS_PACKET
+ SERVICE_CLASS_DATA_SYNC)) {
return false;
}
return true;
} catch (RuntimeException exc) {
Rlog.d(LOG_TAG, "Invalid service class " + exc);
}
} else if (isPinPukCommand()
|| (mSc != null
&& (mSc.equals(SC_PWD) || mSc.equals(SC_CLIP) || mSc.equals(SC_CLIR)))) {
return false;
} else if (mPoundString != null) return true;
return false;
}
/*
* The below actions are IMS/Volte CallBarring actions.We have not defined
* these actions in ImscommandInterface.However we have reused existing
* actions of CallForwarding as, both CF and CB actions are used for same
* purpose.
*/
public int callBarAction(String dialingNumber) {
if (isActivate()) {
return CommandsInterface.CF_ACTION_ENABLE;
} else if (isDeactivate()) {
return CommandsInterface.CF_ACTION_DISABLE;
} else if (isRegister()) {
if (!isEmptyOrNull(dialingNumber)) {
return CommandsInterface.CF_ACTION_REGISTRATION;
} else {
throw new RuntimeException ("invalid action");
}
} else if (isErasure()) {
return CommandsInterface.CF_ACTION_ERASURE;
} else {
throw new RuntimeException ("invalid action");
}
}
/** Process a MMI code or short code...anything that isn't a dialing number */
@UnsupportedAppUsage
public void
processCode () throws CallStateException {
try {
if (isShortCode()) {
Rlog.d(LOG_TAG, "processCode: isShortCode");
// These just get treated as USSD.
Rlog.d(LOG_TAG, "processCode: Sending short code '"
+ mDialingNumber + "' over CS pipe.");
throw new CallStateException(Phone.CS_FALLBACK);
} else if (isServiceCodeCallForwarding(mSc)) {
Rlog.d(LOG_TAG, "processCode: is CF");
String dialingNumber = mSia;
int reason = scToCallForwardReason(mSc);
int serviceClass = siToServiceClass(mSib);
int time = siToTime(mSic);
if (isInterrogate()) {
mPhone.getCallForwardingOption(reason,
obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
} else {
int cfAction;
if (isActivate()) {
// 3GPP TS 22.030 6.5.2
// a call forwarding request with a single * would be
// interpreted as registration if containing a forwarded-to
// number, or an activation if not
if (isEmptyOrNull(dialingNumber)) {
cfAction = CommandsInterface.CF_ACTION_ENABLE;
mIsCallFwdReg = false;
} else {
cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
mIsCallFwdReg = true;
}
} else if (isDeactivate()) {
cfAction = CommandsInterface.CF_ACTION_DISABLE;
} else if (isRegister()) {
cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
} else if (isErasure()) {
cfAction = CommandsInterface.CF_ACTION_ERASURE;
} else {
throw new RuntimeException ("invalid action");
}
int isSettingUnconditional =
((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
(reason == CommandsInterface.CF_REASON_ALL)) ? 1 : 0;
int isEnableDesired =
((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
(cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
Rlog.d(LOG_TAG, "processCode: is CF setCallForward");
mPhone.setCallForwardingOption(cfAction, reason,
dialingNumber, serviceClass, time, obtainMessage(
EVENT_SET_CFF_COMPLETE,
isSettingUnconditional,
isEnableDesired, this));
}
} else if (isServiceCodeCallBarring(mSc)) {
// sia = password
// sib = basic service group
// service group is not supported
String password = mSia;
String facility = scToBarringFacility(mSc);
int serviceClass = siToServiceClass(mSib);
if (isInterrogate()) {
mPhone.getCallBarring(facility,
obtainMessage(EVENT_SUPP_SVC_QUERY_COMPLETE, this), serviceClass);
} else if (isActivate() || isDeactivate()) {
mPhone.setCallBarring(facility, isActivate(), password,
obtainMessage(EVENT_SET_COMPLETE, this), serviceClass);
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (mSc != null && mSc.equals(SC_CLIR)) {
// NOTE: Since these supplementary services are accessed only
// via MMI codes, methods have not been added to ImsPhone.
// Only the UT interface handle is used.
if (isActivate()) {
try {
mPhone.mCT.getUtInterface().updateCLIR(CommandsInterface.CLIR_INVOCATION,
obtainMessage(EVENT_SET_COMPLETE, this));
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCLIR.");
}
} else if (isDeactivate()) {
try {
mPhone.mCT.getUtInterface().updateCLIR(CommandsInterface.CLIR_SUPPRESSION,
obtainMessage(EVENT_SET_COMPLETE, this));
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCLIR.");
}
} else if (isInterrogate()) {
try {
mPhone.mCT.getUtInterface()
.queryCLIR(obtainMessage(EVENT_GET_CLIR_COMPLETE, this));
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for queryCLIR.");
}
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (mSc != null && mSc.equals(SC_CLIP)) {
// NOTE: Refer to the note above.
if (isInterrogate()) {
try {
mPhone.mCT.getUtInterface()
.queryCLIP(obtainMessage(EVENT_SUPP_SVC_QUERY_COMPLETE, this));
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for queryCLIP.");
}
} else if (isActivate() || isDeactivate()) {
try {
mPhone.mCT.getUtInterface().updateCLIP(isActivate(),
obtainMessage(EVENT_SET_COMPLETE, this));
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCLIP.");
}
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (mSc != null && mSc.equals(SC_COLP)) {
// NOTE: Refer to the note above.
if (isInterrogate()) {
try {
mPhone.mCT.getUtInterface()
.queryCOLP(obtainMessage(EVENT_SUPP_SVC_QUERY_COMPLETE, this));
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for queryCOLP.");
}
} else if (isActivate() || isDeactivate()) {
try {
mPhone.mCT.getUtInterface().updateCOLP(isActivate(),
obtainMessage(EVENT_SET_COMPLETE, this));
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCOLP.");
}
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (mSc != null && mSc.equals(SC_COLR)) {
// NOTE: Refer to the note above.
if (isActivate()) {
try {
mPhone.mCT.getUtInterface().updateCOLR(NUM_PRESENTATION_RESTRICTED,
obtainMessage(EVENT_SET_COMPLETE, this));
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCOLR.");
}
} else if (isDeactivate()) {
try {
mPhone.mCT.getUtInterface().updateCOLR(NUM_PRESENTATION_ALLOWED,
obtainMessage(EVENT_SET_COMPLETE, this));
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCOLR.");
}
} else if (isInterrogate()) {
try {
mPhone.mCT.getUtInterface()
.queryCOLR(obtainMessage(EVENT_SUPP_SVC_QUERY_COMPLETE, this));
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for queryCOLR.");
}
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (mSc != null && (mSc.equals(SC_BS_MT))) {
try {
if (isInterrogate()) {
mPhone.mCT.getUtInterface()
.queryCallBarring(ImsUtInterface.CB_BS_MT,
obtainMessage(EVENT_QUERY_ICB_COMPLETE,this));
} else {
processIcbMmiCodeForUpdate();
}
// TODO: isRegister() case needs to be handled.
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for ICB.");
}
} else if (mSc != null && mSc.equals(SC_BAICa)) {
int callAction =0;
// TODO: Should we route through queryCallBarring() here?
try {
if (isInterrogate()) {
mPhone.mCT.getUtInterface()
.queryCallBarring(ImsUtInterface.CB_BIC_ACR,
obtainMessage(EVENT_QUERY_ICB_COMPLETE,this));
} else {
if (isActivate()) {
callAction = CommandsInterface.CF_ACTION_ENABLE;
} else if (isDeactivate()) {
callAction = CommandsInterface.CF_ACTION_DISABLE;
}
mPhone.mCT.getUtInterface()
.updateCallBarring(ImsUtInterface.CB_BIC_ACR,
callAction,
obtainMessage(EVENT_SET_COMPLETE,this),
null);
}
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processCode: Could not get UT handle for ICBa.");
}
} else if (mSc != null && mSc.equals(SC_WAIT)) {
// sia = basic service group
int serviceClass = siToServiceClass(mSib);
if (isActivate() || isDeactivate()) {
mPhone.setCallWaiting(isActivate(), serviceClass,
obtainMessage(EVENT_SET_COMPLETE, this));
} else if (isInterrogate()) {
mPhone.getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this));
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (mPoundString != null) {
// We'll normally send USSD over the CS pipe, but if it happens that the CS phone
// is out of service, we'll just try over IMS instead.
if (mPhone.getDefaultPhone().getServiceStateTracker().mSS.getState()
== STATE_IN_SERVICE) {
Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
+ Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe.");
throw new CallStateException(Phone.CS_FALLBACK);
} else {
Rlog.i(LOG_TAG, "processCode: CS is out of service, sending ussd string '"
+ Rlog.pii(LOG_TAG, mPoundString) + "' over IMS pipe.");
sendUssd(mPoundString);
}
} else {
Rlog.d(LOG_TAG, "processCode: invalid or unsupported MMI");
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} catch (RuntimeException exc) {
mState = State.FAILED;
mMessage = mContext.getText(com.android.internal.R.string.mmiError);
Rlog.d(LOG_TAG, "processCode: RuntimeException = " + exc);
mPhone.onMMIDone(this);
}
}
/**
* Called from ImsPhone
*
* An unsolicited USSD NOTIFY or REQUEST has come in matching
* up with this pending USSD request
*
* Note: If REQUEST, this exchange is complete, but the session remains
* active (ie, the network expects user input).
*/
void
onUssdFinished(String ussdMessage, boolean isUssdRequest) {
if (mState == State.PENDING) {
if (TextUtils.isEmpty(ussdMessage)) {
mMessage = mContext.getText(com.android.internal.R.string.mmiComplete);
Rlog.v(LOG_TAG, "onUssdFinished: no message; using: " + mMessage);
} else {
Rlog.v(LOG_TAG, "onUssdFinished: message: " + ussdMessage);
mMessage = ussdMessage;
}
mIsUssdRequest = isUssdRequest;
// If it's a request, leave it PENDING so that it's cancelable.
if (!isUssdRequest) {
mState = State.COMPLETE;
}
mPhone.onMMIDone(this);
}
}
/**
* Called from ImsPhone
*
* The radio has reset, and this is still pending
*/
void
onUssdFinishedError() {
if (mState == State.PENDING) {
mState = State.FAILED;
mMessage = mContext.getText(com.android.internal.R.string.mmiError);
Rlog.d(LOG_TAG, "onUssdFinishedError: mmi=" + this);
mPhone.onMMIDone(this);
}
}
void sendUssd(String ussdMessage) {
// Treat this as a USSD string
mIsPendingUSSD = true;
// Note that unlike most everything else, the USSD complete
// response does not complete this MMI code...we wait for
// an unsolicited USSD "Notify" or "Request".
// The matching up of this is done in ImsPhone.
mPhone.sendUSSD(ussdMessage,
obtainMessage(EVENT_USSD_COMPLETE, this));
}
/** Called from ImsPhone.handleMessage; not a Handler subclass */
@Override
public void
handleMessage (Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_SET_COMPLETE:
ar = (AsyncResult) (msg.obj);
onSetComplete(msg, ar);
break;
case EVENT_SET_CFF_COMPLETE:
ar = (AsyncResult) (msg.obj);
/*
* msg.arg1 = 1 means to set unconditional voice call forwarding
* msg.arg2 = 1 means to enable voice call forwarding
*/
if ((ar.exception == null) && (msg.arg1 == 1)) {
boolean cffEnabled = (msg.arg2 == 1);
if (mIccRecords != null) {
mPhone.setVoiceCallForwardingFlag(1, cffEnabled, mDialingNumber);
}
}
onSetComplete(msg, ar);
break;
case EVENT_QUERY_CF_COMPLETE:
ar = (AsyncResult) (msg.obj);
onQueryCfComplete(ar);
break;
case EVENT_QUERY_COMPLETE:
ar = (AsyncResult) (msg.obj);
onQueryComplete(ar);
break;
case EVENT_USSD_COMPLETE:
ar = (AsyncResult) (msg.obj);
if (ar.exception != null) {
mState = State.FAILED;
mMessage = getErrorMessage(ar);
mPhone.onMMIDone(this);
}
// Note that unlike most everything else, the USSD complete
// response does not complete this MMI code...we wait for
// an unsolicited USSD "Notify" or "Request".
// The matching up of this is done in ImsPhone.
break;
case EVENT_USSD_CANCEL_COMPLETE:
mPhone.onMMIDone(this);
break;
case EVENT_SUPP_SVC_QUERY_COMPLETE:
ar = (AsyncResult) (msg.obj);
onSuppSvcQueryComplete(ar);
break;
case EVENT_QUERY_ICB_COMPLETE:
ar = (AsyncResult) (msg.obj);
onIcbQueryComplete(ar);
break;
case EVENT_GET_CLIR_COMPLETE:
ar = (AsyncResult) (msg.obj);
onQueryClirComplete(ar);
break;
default:
break;
}
}
//***** Private instance methods
private void
processIcbMmiCodeForUpdate () {
String dialingNumber = mSia;
String[] icbNum = null;
int callAction;
if (dialingNumber != null) {
icbNum = dialingNumber.split("\\$");
}
callAction = callBarAction(dialingNumber);
try {
mPhone.mCT.getUtInterface()
.updateCallBarring(ImsUtInterface.CB_BS_MT,
callAction,
obtainMessage(EVENT_SET_COMPLETE,this),
icbNum);
} catch (ImsException e) {
Rlog.d(LOG_TAG, "processIcbMmiCodeForUpdate:Could not get UT handle for updating ICB.");
}
}
@UnsupportedAppUsage
private CharSequence getErrorMessage(AsyncResult ar) {
CharSequence errorMessage;
return ((errorMessage = getMmiErrorMessage(ar)) != null) ? errorMessage :
mContext.getText(com.android.internal.R.string.mmiError);
}
private CharSequence getMmiErrorMessage(AsyncResult ar) {
if (ar.exception instanceof ImsException) {
switch (((ImsException) ar.exception).getCode()) {
case ImsReasonInfo.CODE_FDN_BLOCKED:
return mContext.getText(com.android.internal.R.string.mmiFdnError);
case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_DIAL:
return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_dial);
case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_USSD:
return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_ussd);
case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_SS:
return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_ss);
case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO:
return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_dial_video);
default:
return null;
}
} else if (ar.exception instanceof CommandException) {
CommandException err = (CommandException) ar.exception;
if (err.getCommandError() == CommandException.Error.FDN_CHECK_FAILURE) {
return mContext.getText(com.android.internal.R.string.mmiFdnError);
} else if (err.getCommandError() == CommandException.Error.SS_MODIFIED_TO_DIAL) {
return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_dial);
} else if (err.getCommandError() == CommandException.Error.SS_MODIFIED_TO_USSD) {
return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_ussd);
} else if (err.getCommandError() == CommandException.Error.SS_MODIFIED_TO_SS) {
return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_ss);
} else if (err.getCommandError() == CommandException.Error.SS_MODIFIED_TO_DIAL_VIDEO) {
return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_dial_video);
}
}
return null;
}
@UnsupportedAppUsage
private CharSequence getScString() {
if (mSc != null) {
if (isServiceCodeCallBarring(mSc)) {
return mContext.getText(com.android.internal.R.string.BaMmi);
} else if (isServiceCodeCallForwarding(mSc)) {
return mContext.getText(com.android.internal.R.string.CfMmi);
} else if (mSc.equals(SC_PWD)) {
return mContext.getText(com.android.internal.R.string.PwdMmi);
} else if (mSc.equals(SC_WAIT)) {
return mContext.getText(com.android.internal.R.string.CwMmi);
} else if (mSc.equals(SC_CLIP)) {
return mContext.getText(com.android.internal.R.string.ClipMmi);
} else if (mSc.equals(SC_CLIR)) {
return mContext.getText(com.android.internal.R.string.ClirMmi);
} else if (mSc.equals(SC_COLP)) {
return mContext.getText(com.android.internal.R.string.ColpMmi);
} else if (mSc.equals(SC_COLR)) {
return mContext.getText(com.android.internal.R.string.ColrMmi);
} else if (mSc.equals(SC_BS_MT)) {
return IcbDnMmi;
} else if (mSc.equals(SC_BAICa)) {
return IcbAnonymousMmi;
}
}
return "";
}
private void
onSetComplete(Message msg, AsyncResult ar){
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
if (ar.exception != null) {
mState = State.FAILED;
if (ar.exception instanceof CommandException) {
CommandException err = (CommandException) ar.exception;
CharSequence errorMessage;
if (err.getCommandError() == CommandException.Error.PASSWORD_INCORRECT) {
sb.append(mContext.getText(
com.android.internal.R.string.passwordIncorrect));
} else if ((errorMessage = getMmiErrorMessage(ar)) != null) {
sb.append(errorMessage);
} else if (err.getMessage() != null) {
sb.append(err.getMessage());
} else {
sb.append(mContext.getText(com.android.internal.R.string.mmiError));
}
} else if (ar.exception instanceof ImsException) {
sb.append(getImsErrorMessage(ar));
}
} else if (isActivate()) {
mState = State.COMPLETE;
if (mIsCallFwdReg) {
sb.append(mContext.getText(
com.android.internal.R.string.serviceRegistered));
} else {
sb.append(mContext.getText(
com.android.internal.R.string.serviceEnabled));
}
// Record CLIR setting
if (mSc.equals(SC_CLIR)) {
mPhone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
}
} else if (isDeactivate()) {
mState = State.COMPLETE;
sb.append(mContext.getText(
com.android.internal.R.string.serviceDisabled));
// Record CLIR setting
if (mSc.equals(SC_CLIR)) {
mPhone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
}
} else if (isRegister()) {
mState = State.COMPLETE;
sb.append(mContext.getText(
com.android.internal.R.string.serviceRegistered));
} else if (isErasure()) {
mState = State.COMPLETE;
sb.append(mContext.getText(
com.android.internal.R.string.serviceErased));
} else {
mState = State.FAILED;
sb.append(mContext.getText(
com.android.internal.R.string.mmiError));
}
mMessage = sb;
Rlog.d(LOG_TAG, "onSetComplete: mmi=" + this);
mPhone.onMMIDone(this);
}
/**
* @param serviceClass 1 bit of the service class bit vectory
* @return String to be used for call forward query MMI response text.
* Returns null if unrecognized
*/
@UnsupportedAppUsage
private CharSequence
serviceClassToCFString (int serviceClass) {
switch (serviceClass) {
case SERVICE_CLASS_VOICE:
return mContext.getText(com.android.internal.R.string.serviceClassVoice);
case SERVICE_CLASS_DATA:
return mContext.getText(com.android.internal.R.string.serviceClassData);
case SERVICE_CLASS_FAX:
return mContext.getText(com.android.internal.R.string.serviceClassFAX);
case SERVICE_CLASS_SMS:
return mContext.getText(com.android.internal.R.string.serviceClassSMS);
case SERVICE_CLASS_DATA_SYNC:
return mContext.getText(com.android.internal.R.string.serviceClassDataSync);
case SERVICE_CLASS_DATA_ASYNC:
return mContext.getText(com.android.internal.R.string.serviceClassDataAsync);
case SERVICE_CLASS_PACKET:
return mContext.getText(com.android.internal.R.string.serviceClassPacket);
case SERVICE_CLASS_PAD:
return mContext.getText(com.android.internal.R.string.serviceClassPAD);
default:
return null;
}
}
/** one CallForwardInfo + serviceClassMask -> one line of text */
private CharSequence
makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) {
CharSequence template;
String sources[] = {"{0}", "{1}", "{2}"};
CharSequence destinations[] = new CharSequence[3];
boolean needTimeTemplate;
// CF_REASON_NO_REPLY also has a time value associated with
// it. All others don't.
needTimeTemplate =
(info.reason == CommandsInterface.CF_REASON_NO_REPLY);
if (info.status == 1) {
if (needTimeTemplate) {
template = mContext.getText(
com.android.internal.R.string.cfTemplateForwardedTime);
} else {
template = mContext.getText(
com.android.internal.R.string.cfTemplateForwarded);
}
} else if (info.status == 0 && isEmptyOrNull(info.number)) {
template = mContext.getText(
com.android.internal.R.string.cfTemplateNotForwarded);
} else { /* (info.status == 0) && !isEmptyOrNull(info.number) */
// A call forward record that is not active but contains
// a phone number is considered "registered"
if (needTimeTemplate) {
template = mContext.getText(
com.android.internal.R.string.cfTemplateRegisteredTime);
} else {
template = mContext.getText(
com.android.internal.R.string.cfTemplateRegistered);
}
}
// In the template (from strings.xmls)
// {0} is one of "bearerServiceCode*"
// {1} is dialing number
// {2} is time in seconds
destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa);
destinations[2] = Integer.toString(info.timeSeconds);
if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL &&
(info.serviceClass & serviceClassMask)
== CommandsInterface.SERVICE_CLASS_VOICE) {
boolean cffEnabled = (info.status == 1);
if (mIccRecords != null) {
mPhone.setVoiceCallForwardingFlag(1, cffEnabled, info.number);
}
}
return TextUtils.replace(template, sources, destinations);
}
private void
onQueryCfComplete(AsyncResult ar) {
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
if (ar.exception != null) {
mState = State.FAILED;
if (ar.exception instanceof ImsException) {
sb.append(getImsErrorMessage(ar));
}
else {
sb.append(getErrorMessage(ar));
}
} else {
CallForwardInfo infos[];
infos = (CallForwardInfo[]) ar.result;
if (infos == null || infos.length == 0) {
// Assume the default is not active
sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
// Set unconditional CFF in SIM to false
if (mIccRecords != null) {
mPhone.setVoiceCallForwardingFlag(1, false, null);
}
} else {
SpannableStringBuilder tb = new SpannableStringBuilder();
// Each bit in the service class gets its own result line
// The service classes may be split up over multiple
// CallForwardInfos. So, for each service class, find out
// which CallForwardInfo represents it and then build
// the response text based on that
for (int serviceClassMask = 1
; serviceClassMask <= SERVICE_CLASS_MAX
; serviceClassMask <<= 1
) {
for (int i = 0, s = infos.length; i < s ; i++) {
if ((serviceClassMask & infos[i].serviceClass) != 0) {
tb.append(makeCFQueryResultMessage(infos[i],
serviceClassMask));
tb.append("\n");
}
}
}
sb.append(tb);
}
mState = State.COMPLETE;
}
mMessage = sb;
Rlog.d(LOG_TAG, "onQueryCfComplete: mmi=" + this);
mPhone.onMMIDone(this);
}
private void onSuppSvcQueryComplete(AsyncResult ar) {
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
mState = State.FAILED;
if (ar.exception != null) {
if (ar.exception instanceof ImsException) {
sb.append(getImsErrorMessage(ar));
} else {
sb.append(getErrorMessage(ar));
}
} else {
ImsSsInfo ssInfo = null;
if (ar.result instanceof Bundle) {
Rlog.d(LOG_TAG, "onSuppSvcQueryComplete: Received CLIP/COLP/COLR Response.");
// Response for CLIP, COLP and COLR queries.
Bundle ssInfoResp = (Bundle) ar.result;
ssInfo = (ImsSsInfo) ssInfoResp.getParcelable(UT_BUNDLE_KEY_SSINFO);
if (ssInfo != null) {
Rlog.d(LOG_TAG,
"onSuppSvcQueryComplete: ImsSsInfo mStatus = " + ssInfo.getStatus());
if (ssInfo.getProvisionStatus() == ImsSsInfo.SERVICE_NOT_PROVISIONED) {
sb.append(mContext.getText(
com.android.internal.R.string.serviceNotProvisioned));
mState = State.COMPLETE;
} else if (ssInfo.getStatus() == ImsSsInfo.DISABLED) {
sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
mState = State.COMPLETE;
} else if (ssInfo.getStatus() == ImsSsInfo.ENABLED) {
sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled));
mState = State.COMPLETE;
} else {
sb.append(mContext.getText(com.android.internal.R.string.mmiError));
}
} else {
sb.append(mContext.getText(com.android.internal.R.string.mmiError));
}
} else {
Rlog.d(LOG_TAG, "onSuppSvcQueryComplete: Received Call Barring Response.");
// Response for Call Barring queries.
int[] cbInfos = (int[]) ar.result;
if (cbInfos[0] == 1) {
sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled));
mState = State.COMPLETE;
} else {
sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
mState = State.COMPLETE;
}
}
}
mMessage = sb;
Rlog.d(LOG_TAG, "onSuppSvcQueryComplete mmi=" + this);
mPhone.onMMIDone(this);
}
private void onIcbQueryComplete(AsyncResult ar) {
Rlog.d(LOG_TAG, "onIcbQueryComplete mmi=" + this);
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
if (ar.exception != null) {
mState = State.FAILED;
if (ar.exception instanceof ImsException) {
sb.append(getImsErrorMessage(ar));
} else {
sb.append(getErrorMessage(ar));
}
} else {
List<ImsSsInfo> infos = null;
try {
infos = (List<ImsSsInfo>) ar.result;
} catch (ClassCastException cce) {
// TODO in R: #157# still has ImsSsInfo[] type, fix the type in IImsUtListener.aidl.
infos = Arrays.asList((ImsSsInfo[]) ar.result);
}
if (infos == null || infos.size() == 0) {
sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
} else {
ImsSsInfo info;
for (int i = 0, s = infos.size(); i < s; i++) {
info = infos.get(i);
if (info.getIncomingCommunicationBarringNumber() != null) {
sb.append("Num: " + info.getIncomingCommunicationBarringNumber()
+ " status: " + info.getStatus() + "\n");
} else if (info.getStatus() == 1) {
sb.append(mContext.getText(com.android.internal
.R.string.serviceEnabled));
} else {
sb.append(mContext.getText(com.android.internal
.R.string.serviceDisabled));
}
}
}
mState = State.COMPLETE;
}
mMessage = sb;
mPhone.onMMIDone(this);
}
private void onQueryClirComplete(AsyncResult ar) {
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
mState = State.FAILED;
if (ar.exception != null) {
if (ar.exception instanceof ImsException) {
sb.append(getImsErrorMessage(ar));
}
} else {
Bundle ssInfo = (Bundle) ar.result;
int[] clirInfo = ssInfo.getIntArray(UT_BUNDLE_KEY_CLIR);
// clirInfo[0] = The 'n' parameter from TS 27.007 7.7
// clirInfo[1] = The 'm' parameter from TS 27.007 7.7
Rlog.d(LOG_TAG, "onQueryClirComplete: CLIR param n=" + clirInfo[0]
+ " m=" + clirInfo[1]);
// 'm' parameter.
switch (clirInfo[1]) {
case ImsSsInfo.CLIR_STATUS_NOT_PROVISIONED:
sb.append(mContext.getText(
com.android.internal.R.string.serviceNotProvisioned));
mState = State.COMPLETE;
break;
case ImsSsInfo.CLIR_STATUS_PROVISIONED_PERMANENT:
sb.append(mContext.getText(
com.android.internal.R.string.CLIRPermanent));
mState = State.COMPLETE;
break;
case ImsSsInfo.CLIR_STATUS_TEMPORARILY_RESTRICTED:
// 'n' parameter.
switch (clirInfo[0]) {
case ImsSsInfo.CLIR_OUTGOING_DEFAULT:
sb.append(mContext.getText(
com.android.internal.R.string.CLIRDefaultOnNextCallOn));
mState = State.COMPLETE;
break;
case ImsSsInfo.CLIR_OUTGOING_INVOCATION:
sb.append(mContext.getText(
com.android.internal.R.string.CLIRDefaultOnNextCallOn));
mState = State.COMPLETE;
break;
case ImsSsInfo.CLIR_OUTGOING_SUPPRESSION:
sb.append(mContext.getText(
com.android.internal.R.string.CLIRDefaultOnNextCallOff));
mState = State.COMPLETE;
break;
default:
sb.append(mContext.getText(
com.android.internal.R.string.mmiError));
mState = State.FAILED;
}
break;
case ImsSsInfo.CLIR_STATUS_TEMPORARILY_ALLOWED:
// 'n' parameter.
switch (clirInfo[0]) {
case ImsSsInfo.CLIR_OUTGOING_DEFAULT:
sb.append(mContext.getText(
com.android.internal.R.string.CLIRDefaultOffNextCallOff));
mState = State.COMPLETE;
break;
case ImsSsInfo.CLIR_OUTGOING_INVOCATION:
sb.append(mContext.getText(
com.android.internal.R.string.CLIRDefaultOffNextCallOn));
mState = State.COMPLETE;
break;
case ImsSsInfo.CLIR_OUTGOING_SUPPRESSION:
sb.append(mContext.getText(
com.android.internal.R.string.CLIRDefaultOffNextCallOff));
mState = State.COMPLETE;
break;
default:
sb.append(mContext.getText(
com.android.internal.R.string.mmiError));
mState = State.FAILED;
}
break;
default:
sb.append(mContext.getText(
com.android.internal.R.string.mmiError));
mState = State.FAILED;
}
}
mMessage = sb;
Rlog.d(LOG_TAG, "onQueryClirComplete mmi=" + this);
mPhone.onMMIDone(this);
}
private void
onQueryComplete(AsyncResult ar) {
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
if (ar.exception != null) {
mState = State.FAILED;
if (ar.exception instanceof ImsException) {
sb.append(getImsErrorMessage(ar));
} else {
sb.append(getErrorMessage(ar));
}
} else {
int[] ints = (int[])ar.result;
if (ints.length != 0) {
if (ints[0] == 0) {
sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
} else if (mSc.equals(SC_WAIT)) {
// Call Waiting includes additional data in the response.
sb.append(createQueryCallWaitingResultMessage(ints[1]));
} else if (ints[0] == 1) {
// for all other services, treat it as a boolean
sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled));
} else {
sb.append(mContext.getText(com.android.internal.R.string.mmiError));
}
} else {
sb.append(mContext.getText(com.android.internal.R.string.mmiError));
}
mState = State.COMPLETE;
}
mMessage = sb;
Rlog.d(LOG_TAG, "onQueryComplete mmi=" + this);
mPhone.onMMIDone(this);
}
private CharSequence
createQueryCallWaitingResultMessage(int serviceClass) {
StringBuilder sb = new StringBuilder(
mContext.getText(com.android.internal.R.string.serviceEnabledFor));
for (int classMask = 1
; classMask <= SERVICE_CLASS_MAX
; classMask <<= 1
) {
if ((classMask & serviceClass) != 0) {
sb.append("\n");
sb.append(serviceClassToCFString(classMask & serviceClass));
}
}
return sb;
}
private CharSequence getImsErrorMessage(AsyncResult ar) {
ImsException error = (ImsException) ar.exception;
CharSequence errorMessage;
if ((errorMessage = getMmiErrorMessage(ar)) != null) {
return errorMessage;
} else if (error.getMessage() != null) {
return error.getMessage();
} else {
return getErrorMessage(ar);
}
}
@Override
public ResultReceiver getUssdCallbackReceiver() {
return this.mCallbackReceiver;
}
/**
* Process IMS SS Data received.
*/
public void processImsSsData(AsyncResult data) throws ImsException {
try {
ImsSsData ssData = (ImsSsData) data.result;
parseSsData(ssData);
} catch (ClassCastException | NullPointerException ex) {
throw new ImsException("Exception in parsing SS Data", 0);
}
}
void parseSsData(ImsSsData ssData) {
ImsException ex = (ssData.getResult() != ImsSsData.RESULT_SUCCESS)
? new ImsException(null, ssData.getResult()) : null;
mSc = getScStringFromScType(ssData.getServiceType());
mAction = getActionStringFromReqType(ssData.getRequestType());
Rlog.d(LOG_TAG, "parseSsData msc = " + mSc + ", action = " + mAction + ", ex = " + ex);
switch (ssData.getRequestType()) {
case ImsSsData.SS_ACTIVATION:
case ImsSsData.SS_DEACTIVATION:
case ImsSsData.SS_REGISTRATION:
case ImsSsData.SS_ERASURE:
if ((ssData.getResult() == ImsSsData.RESULT_SUCCESS)
&& ssData.isTypeUnConditional()) {
/*
* When ssData.serviceType is unconditional (SS_CFU or SS_CF_ALL) and
* ssData.requestType is activate/register and
* ServiceClass is Voice/Video/None, turn on voice call forwarding.
*/
boolean cffEnabled = ((ssData.getRequestType() == ImsSsData.SS_ACTIVATION
|| ssData.getRequestType() == ImsSsData.SS_REGISTRATION)
&& isServiceClassVoiceVideoOrNone(ssData.getServiceClass()));
Rlog.d(LOG_TAG, "setCallForwardingFlag cffEnabled: " + cffEnabled);
if (mIccRecords != null) {
Rlog.d(LOG_TAG, "setVoiceCallForwardingFlag done from SS Info.");
//Only CF status is set here as part of activation/registration,
//number is not available until interrogation.
mPhone.setVoiceCallForwardingFlag(1, cffEnabled, null);
} else {
Rlog.e(LOG_TAG, "setCallForwardingFlag aborted. sim records is null.");
}
}
onSetComplete(null, new AsyncResult(null, ssData.getCallForwardInfo(), ex));
break;
case ImsSsData.SS_INTERROGATION:
if (ssData.isTypeClir()) {
Rlog.d(LOG_TAG, "CLIR INTERROGATION");
Bundle clirInfo = new Bundle();
clirInfo.putIntArray(UT_BUNDLE_KEY_CLIR, ssData.getSuppServiceInfoCompat());
onQueryClirComplete(new AsyncResult(null, clirInfo, ex));
} else if (ssData.isTypeCF()) {
Rlog.d(LOG_TAG, "CALL FORWARD INTERROGATION");
// Have to translate to an array, since the modem still returns it in the
// ImsCallForwardInfo[] format.
List<ImsCallForwardInfo> mCfInfos = ssData.getCallForwardInfo();
ImsCallForwardInfo[] mCfInfosCompat = null;
if (mCfInfos != null) {
mCfInfosCompat = new ImsCallForwardInfo[mCfInfos.size()];
mCfInfosCompat = mCfInfos.toArray(mCfInfosCompat);
}
onQueryCfComplete(new AsyncResult(null, mPhone.handleCfQueryResult(
mCfInfosCompat), ex));
} else if (ssData.isTypeBarring()) {
onSuppSvcQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfoCompat(),
ex));
} else if (ssData.isTypeColr() || ssData.isTypeClip() || ssData.isTypeColp()) {
int[] suppServiceInfo = ssData.getSuppServiceInfoCompat();
ImsSsInfo ssInfo = new ImsSsInfo.Builder(suppServiceInfo[0]).build();
Bundle clInfo = new Bundle();
clInfo.putParcelable(UT_BUNDLE_KEY_SSINFO, ssInfo);
onSuppSvcQueryComplete(new AsyncResult(null, clInfo, ex));
} else if (ssData.isTypeIcb()) {
onIcbQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfo(), ex));
} else {
onQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfoCompat(), ex));
}
break;
default:
Rlog.e(LOG_TAG, "Invaid requestType in SSData : " + ssData.getRequestType());
break;
}
}
private String getScStringFromScType(int serviceType) {
switch (serviceType) {
case ImsSsData.SS_CFU:
return SC_CFU;
case ImsSsData.SS_CF_BUSY:
return SC_CFB;
case ImsSsData.SS_CF_NO_REPLY:
return SC_CFNRy;
case ImsSsData.SS_CF_NOT_REACHABLE:
return SC_CFNR;
case ImsSsData.SS_CF_ALL:
return SC_CF_All;
case ImsSsData.SS_CF_ALL_CONDITIONAL:
return SC_CF_All_Conditional;
case ImsSsData.SS_CLIP:
return SC_CLIP;
case ImsSsData.SS_CLIR:
return SC_CLIR;
case ImsSsData.SS_COLP:
return SC_COLP;
case ImsSsData.SS_COLR:
return SC_COLR;
case ImsSsData.SS_CNAP:
return SC_CNAP;
case ImsSsData.SS_WAIT:
return SC_WAIT;
case ImsSsData.SS_BAOC:
return SC_BAOC;
case ImsSsData.SS_BAOIC:
return SC_BAOIC;
case ImsSsData.SS_BAOIC_EXC_HOME:
return SC_BAOICxH;
case ImsSsData.SS_BAIC:
return SC_BAIC;
case ImsSsData.SS_BAIC_ROAMING:
return SC_BAICr;
case ImsSsData.SS_ALL_BARRING:
return SC_BA_ALL;
case ImsSsData.SS_OUTGOING_BARRING:
return SC_BA_MO;
case ImsSsData.SS_INCOMING_BARRING:
return SC_BA_MT;
case ImsSsData.SS_INCOMING_BARRING_DN:
return SC_BS_MT;
case ImsSsData.SS_INCOMING_BARRING_ANONYMOUS:
return SC_BAICa;
default:
return null;
}
}
private String getActionStringFromReqType(int requestType) {
switch (requestType) {
case ImsSsData.SS_ACTIVATION:
return ACTION_ACTIVATE;
case ImsSsData.SS_DEACTIVATION:
return ACTION_DEACTIVATE;
case ImsSsData.SS_INTERROGATION:
return ACTION_INTERROGATE;
case ImsSsData.SS_REGISTRATION:
return ACTION_REGISTER;
case ImsSsData.SS_ERASURE:
return ACTION_ERASURE;
default:
return null;
}
}
private boolean isServiceClassVoiceVideoOrNone(int serviceClass) {
return ((serviceClass == SERVICE_CLASS_NONE) || (serviceClass == SERVICE_CLASS_VOICE)
|| (serviceClass == (SERVICE_CLASS_PACKET + SERVICE_CLASS_DATA_SYNC)));
}
public boolean isSsInfo() {
return mIsSsInfo;
}
public void setIsSsInfo(boolean isSsInfo) {
mIsSsInfo = isSsInfo;
}
/***
* TODO: It would be nice to have a method here that can take in a dialstring and
* figure out if there is an MMI code embedded within it. This code would replace
* some of the string parsing functionality in the Phone App's
* SpecialCharSequenceMgr class.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("ImsPhoneMmiCode {");
sb.append("State=" + getState());
if (mAction != null) sb.append(" action=" + mAction);
if (mSc != null) sb.append(" sc=" + mSc);
if (mSia != null) sb.append(" sia=" + mSia);
if (mSib != null) sb.append(" sib=" + mSib);
if (mSic != null) sb.append(" sic=" + mSic);
if (mPoundString != null) sb.append(" poundString=" + Rlog.pii(LOG_TAG, mPoundString));
if (mDialingNumber != null) sb.append(" dialingNumber="
+ Rlog.pii(LOG_TAG, mDialingNumber));
if (mPwd != null) sb.append(" pwd=" + Rlog.pii(LOG_TAG, mPwd));
if (mCallbackReceiver != null) sb.append(" hasReceiver");
sb.append("}");
return sb.toString();
}
}