blob: bcbd127497b21b70cc01d3ff1f127c8885aad8d3 [file] [log] [blame]
/*
* Copyright (C) 2006 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.gsm;
import android.content.Context;
import com.android.internal.telephony.*;
import android.os.*;
import android.telephony.PhoneNumberUtils;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.Log;
import static com.android.internal.telephony.CommandsInterface.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
* 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 GsmMmiCode extends Handler implements MmiCode {
static final String LOG_TAG = "GSM";
//***** Constants
// From TS 22.030 6.5.2
static final String ACTION_ACTIVATE = "*";
static final String ACTION_DEACTIVATE = "#";
static final String ACTION_INTERROGATE = "*#";
static final String ACTION_REGISTER = "**";
static final String ACTION_ERASURE = "##";
// Supp Service cocdes from TS 22.030 Annex B
//Called line presentation
static final String SC_CLIP = "30";
static final String SC_CLIR = "31";
// Call Forwarding
static final String SC_CFU = "21";
static final String SC_CFB = "67";
static final String SC_CFNRy = "61";
static final String SC_CFNR = "62";
static final String SC_CF_All = "002";
static final String SC_CF_All_Conditional = "004";
// Call Waiting
static final String SC_WAIT = "43";
// Call Barring
static final String SC_BAOC = "33";
static final String SC_BAOIC = "331";
static final String SC_BAOICxH = "332";
static final String SC_BAIC = "35";
static final String SC_BAICr = "351";
static final String SC_BA_ALL = "330";
static final String SC_BA_MO = "333";
static final String SC_BA_MT = "353";
// Supp Service Password registration
static final String SC_PWD = "03";
// PIN/PIN2/PUK/PUK2
static final String SC_PIN = "04";
static final String SC_PIN2 = "042";
static final String SC_PUK = "05";
static final String SC_PUK2 = "052";
//***** Event Constants
static final int EVENT_SET_COMPLETE = 1;
static final int EVENT_GET_CLIR_COMPLETE = 2;
static final int EVENT_QUERY_CF_COMPLETE = 3;
static final int EVENT_USSD_COMPLETE = 4;
static final int EVENT_QUERY_COMPLETE = 5;
static final int EVENT_SET_CFF_COMPLETE = 6;
static final int EVENT_USSD_CANCEL_COMPLETE = 7;
//***** Instance Variables
GSMPhone phone;
Context context;
String action; // One of ACTION_*
String sc; // Service Code
String sia, sib, sic; // Service Info a,b,c
String poundString; // Entire MMI string up to and including #
String dialingNumber;
String pwd; // For password registration
/** Set to true in processCode, not at newFromDialString time */
private boolean isPendingUSSD;
private boolean isUssdRequest;
State state = State.PENDING;
CharSequence message;
//***** Class Variables
// See TS 22.030 6.5.2 "Structure of the MMI"
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
*/
static final int MATCH_GROUP_POUND_STRING = 1;
static final int MATCH_GROUP_ACTION = 2;
//(activation/interrogation/registration/erasure)
static final int MATCH_GROUP_SERVICE_CODE = 3;
static final int MATCH_GROUP_SIA = 5;
static final int MATCH_GROUP_SIB = 7;
static final int MATCH_GROUP_SIC = 9;
static final int MATCH_GROUP_PWD_CONFIRM = 11;
static final int MATCH_GROUP_DIALING_NUMBER = 12;
//***** Public Class methods
/**
* Some dial strings in GSM are defined to do non-call setup
* things, such as modify or query supplementry 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
*/
static GsmMmiCode
newFromDialString(String dialString, GSMPhone phone) {
Matcher m;
GsmMmiCode ret = null;
m = sPatternSuppService.matcher(dialString);
// Is this formatted like a standard supplementary service code?
if (m.matches()) {
ret = new GsmMmiCode(phone);
ret.poundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING));
ret.action = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
ret.sc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
ret.sia = makeEmptyNull(m.group(MATCH_GROUP_SIA));
ret.sib = makeEmptyNull(m.group(MATCH_GROUP_SIB));
ret.sic = makeEmptyNull(m.group(MATCH_GROUP_SIC));
ret.pwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM));
ret.dialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER));
} 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 GsmMmiCode(phone);
ret.poundString = dialString;
} else if (isShortCode(dialString, phone)) {
// this may be a short code, as defined in TS 22.030, 6.5.3.2
ret = new GsmMmiCode(phone);
ret.dialingNumber = dialString;
}
return ret;
}
static GsmMmiCode
newNetworkInitiatedUssd (String ussdMessage,
boolean isUssdRequest, GSMPhone phone) {
GsmMmiCode ret;
ret = new GsmMmiCode(phone);
ret.message = ussdMessage;
ret.isUssdRequest = isUssdRequest;
// If it's a request, set to PENDING so that it's cancelable.
if (isUssdRequest) {
ret.isPendingUSSD = true;
ret.state = State.PENDING;
} else {
ret.state = State.COMPLETE;
}
return ret;
}
static GsmMmiCode newFromUssdUserInput(String ussdMessge, GSMPhone phone) {
GsmMmiCode ret = new GsmMmiCode(phone);
ret.message = ussdMessge;
ret.state = State.PENDING;
ret.isPendingUSSD = 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;
}
/** returns true of the string is empty or null */
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;
/*
Note for code 20:
From TS 22.030 Annex C:
"All GPRS bearer services" are not included in "All tele and bearer services"
and "All bearer services"."
....so SERVICE_CLASS_DATA, which (according to 27.007) includes GPRS
*/
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) {
return sc != null &&
(sc.equals(SC_BAOC)
|| sc.equals(SC_BAOIC)
|| sc.equals(SC_BAOICxH)
|| sc.equals(SC_BAIC)
|| sc.equals(SC_BAICr)
|| sc.equals(SC_BA_ALL)
|| sc.equals(SC_BA_MO)
|| sc.equals(SC_BA_MT));
}
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
GsmMmiCode (GSMPhone phone) {
// The telephony unit-test cases may create GsmMmiCode's
// in secondary threads
super(phone.getHandler().getLooper());
this.phone = phone;
this.context = phone.getContext();
}
//***** MmiCode implementation
public State
getState() {
return state;
}
public CharSequence
getMessage() {
return message;
}
// inherited javadoc suffices
public void
cancel() {
// Complete or failed cannot be cancelled
if (state == State.COMPLETE || state == State.FAILED) {
return;
}
state = State.CANCELLED;
if (isPendingUSSD) {
/*
* There can only be one pending USSD session, so tell the radio to
* cancel it.
*/
phone.mCM.cancelPendingUssd(obtainMessage(EVENT_USSD_CANCEL_COMPLETE, this));
/*
* Don't call phone.onMMIDone here; wait for CANCEL_COMPLETE notice
* from RIL.
*/
} else {
// TODO in cases other than USSD, it would be nice to cancel
// the pending radio operation. This requires RIL cancellation
// support, which does not presently exist.
phone.onMMIDone (this);
}
}
public boolean isCancelable() {
/* Can only cancel pending USSD sessions. */
return isPendingUSSD;
}
//***** Instance Methods
/** Does this dial string contain a structured or unstructured MMI code? */
boolean
isMMI() {
return poundString != null;
}
/* Is this a 1 or 2 digit "short code" as defined in TS 22.030 sec 6.5.3.2? */
boolean
isShortCode() {
return poundString == null
&& dialingNumber != null && dialingNumber.length() <= 2;
}
/**
* 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, GSMPhone phone) {
// Refer to TS 22.030 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').
return ((dialString != null && dialString.length() <= 2)
&& !PhoneNumberUtils.isEmergencyNumber(dialString)
&& (phone.isInCall()
|| !((dialString.length() == 2 && dialString.charAt(0) == '1')
/* While contrary to TS 22.030, there is strong precendence
* for treating "0" and "00" as call setup strings.
*/
|| dialString.equals("0")
|| dialString.equals("00"))));
}
/**
* @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
*/
boolean isPinCommand() {
return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2)
|| sc.equals(SC_PUK) || sc.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 "
*/
boolean
isTemporaryModeCLIR() {
return sc != null && sc.equals(SC_CLIR) && dialingNumber != null
&& (isActivate() || isDeactivate());
}
/**
* returns CommandsInterface.CLIR_*
* See also isTemporaryModeCLIR()
*/
int
getCLIRMode() {
if (sc != null && sc.equals(SC_CLIR)) {
if (isActivate()) {
return CommandsInterface.CLIR_SUPPRESSION;
} else if (isDeactivate()) {
return CommandsInterface.CLIR_INVOCATION;
}
}
return CommandsInterface.CLIR_DEFAULT;
}
boolean isActivate() {
return action != null && action.equals(ACTION_ACTIVATE);
}
boolean isDeactivate() {
return action != null && action.equals(ACTION_DEACTIVATE);
}
boolean isInterrogate() {
return action != null && action.equals(ACTION_INTERROGATE);
}
boolean isRegister() {
return action != null && action.equals(ACTION_REGISTER);
}
boolean isErasure() {
return action != null && action.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 isPendingUSSD;
}
public boolean isUssdRequest() {
return isUssdRequest;
}
/** Process a MMI code or short code...anything that isn't a dialing number */
void
processCode () {
try {
if (isShortCode()) {
Log.d(LOG_TAG, "isShortCode");
// These just get treated as USSD.
sendUssd(dialingNumber);
} else if (dialingNumber != null) {
// We should have no dialing numbers here
throw new RuntimeException ("Invalid or Unsupported MMI Code");
} else if (sc != null && sc.equals(SC_CLIP)) {
Log.d(LOG_TAG, "is CLIP");
if (isInterrogate()) {
phone.mCM.queryCLIP(
obtainMessage(EVENT_QUERY_COMPLETE, this));
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (sc != null && sc.equals(SC_CLIR)) {
Log.d(LOG_TAG, "is CLIR");
if (isActivate()) {
phone.mCM.setCLIR(CommandsInterface.CLIR_INVOCATION,
obtainMessage(EVENT_SET_COMPLETE, this));
} else if (isDeactivate()) {
phone.mCM.setCLIR(CommandsInterface.CLIR_SUPPRESSION,
obtainMessage(EVENT_SET_COMPLETE, this));
} else if (isInterrogate()) {
phone.mCM.getCLIR(
obtainMessage(EVENT_GET_CLIR_COMPLETE, this));
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (isServiceCodeCallForwarding(sc)) {
Log.d(LOG_TAG, "is CF");
String dialingNumber = sia;
int serviceClass = siToServiceClass(sib);
int reason = scToCallForwardReason(sc);
int time = siToTime(sic);
if (isInterrogate()) {
phone.mCM.queryCallForwardStatus(
reason, serviceClass, dialingNumber,
obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
} else {
int cfAction;
if (isActivate()) {
cfAction = CommandsInterface.CF_ACTION_ENABLE;
} 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 isSettingUnconditionalVoice =
(((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
(reason == CommandsInterface.CF_REASON_ALL)) &&
(((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
(serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
int isEnableDesired =
((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
(cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
Log.d(LOG_TAG, "is CF setCallForward");
phone.mCM.setCallForward(cfAction, reason, serviceClass,
dialingNumber, time, obtainMessage(
EVENT_SET_CFF_COMPLETE,
isSettingUnconditionalVoice,
isEnableDesired, this));
}
} else if (isServiceCodeCallBarring(sc)) {
// sia = password
// sib = basic service group
String password = sia;
int serviceClass = siToServiceClass(sib);
String facility = scToBarringFacility(sc);
if (isInterrogate()) {
phone.mCM.queryFacilityLock(facility, password,
serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this));
} else if (isActivate() || isDeactivate()) {
phone.mCM.setFacilityLock(facility, isActivate(), password,
serviceClass, obtainMessage(EVENT_SET_COMPLETE, this));
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (sc != null && sc.equals(SC_PWD)) {
// sia = fac
// sib = old pwd
// sic = new pwd
// pwd = new pwd
String facility;
String oldPwd = sib;
String newPwd = sic;
if (isActivate() || isRegister()) {
// Even though ACTIVATE is acceptable, this is really termed a REGISTER
action = ACTION_REGISTER;
if (sia == null) {
// If sc was not specified, treat it as BA_ALL.
facility = CommandsInterface.CB_FACILITY_BA_ALL;
} else {
facility = scToBarringFacility(sia);
}
if (newPwd.equals(pwd)) {
phone.mCM.changeBarringPassword(facility, oldPwd,
newPwd, obtainMessage(EVENT_SET_COMPLETE, this));
} else {
// password mismatch; return error
handlePasswordError(com.android.internal.R.string.passwordIncorrect);
}
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (sc != null && sc.equals(SC_WAIT)) {
// sia = basic service group
int serviceClass = siToServiceClass(sia);
if (isActivate() || isDeactivate()) {
phone.mCM.setCallWaiting(isActivate(), serviceClass,
obtainMessage(EVENT_SET_COMPLETE, this));
} else if (isInterrogate()) {
phone.mCM.queryCallWaiting(serviceClass,
obtainMessage(EVENT_QUERY_COMPLETE, this));
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (isPinCommand()) {
// sia = old PIN or PUK
// sib = new PIN
// sic = new PIN
String oldPinOrPuk = sia;
String newPin = sib;
int pinLen = newPin.length();
if (isRegister()) {
if (!newPin.equals(sic)) {
// password mismatch; return error
handlePasswordError(com.android.internal.R.string.mismatchPin);
} else if (pinLen < 4 || pinLen > 8 ) {
// invalid length
handlePasswordError(com.android.internal.R.string.invalidPin);
} else if (sc.equals(SC_PIN) &&
phone.mSimCard.getState() == SimCard.State.PUK_REQUIRED ) {
// Sim is puk-locked
handlePasswordError(com.android.internal.R.string.needPuk);
} else {
// pre-checks OK
if (sc.equals(SC_PIN)) {
phone.mCM.changeIccPin(oldPinOrPuk, newPin,
obtainMessage(EVENT_SET_COMPLETE, this));
} else if (sc.equals(SC_PIN2)) {
phone.mCM.changeIccPin2(oldPinOrPuk, newPin,
obtainMessage(EVENT_SET_COMPLETE, this));
} else if (sc.equals(SC_PUK)) {
phone.mCM.supplyIccPuk(oldPinOrPuk, newPin,
obtainMessage(EVENT_SET_COMPLETE, this));
} else if (sc.equals(SC_PUK2)) {
phone.mCM.supplyIccPuk2(oldPinOrPuk, newPin,
obtainMessage(EVENT_SET_COMPLETE, this));
}
}
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} else if (poundString != null) {
sendUssd(poundString);
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} catch (RuntimeException exc) {
state = State.FAILED;
message = context.getText(com.android.internal.R.string.mmiError);
phone.onMMIDone(this);
}
}
private void handlePasswordError(int res) {
state = State.FAILED;
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
sb.append(context.getText(res));
message = sb;
phone.onMMIDone(this);
}
/**
* Called from GSMPhone
*
* 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 (state == State.PENDING) {
if (ussdMessage == null) {
message = context.getText(com.android.internal.R.string.mmiComplete);
} else {
message = ussdMessage;
}
this.isUssdRequest = isUssdRequest;
// If it's a request, leave it PENDING so that it's cancelable.
if (!isUssdRequest) {
state = State.COMPLETE;
}
phone.onMMIDone(this);
}
}
/**
* Called from GSMPhone
*
* The radio has reset, and this is still pending
*/
void
onUssdFinishedError() {
if (state == State.PENDING) {
state = State.FAILED;
message = context.getText(com.android.internal.R.string.mmiError);
phone.onMMIDone(this);
}
}
void sendUssd(String ussdMessage) {
// Treat this as a USSD string
isPendingUSSD = 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 doene in GSMPhone.
phone.mCM.sendUSSD(ussdMessage,
obtainMessage(EVENT_USSD_COMPLETE, this));
}
/** Called from GSMPhone.handleMessage; not a Handler subclass */
public void
handleMessage (Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_SET_COMPLETE:
ar = (AsyncResult) (msg.obj);
onSetComplete(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);
phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled);
}
onSetComplete(ar);
break;
case EVENT_GET_CLIR_COMPLETE:
ar = (AsyncResult) (msg.obj);
onGetClirComplete(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) {
state = State.FAILED;
message = context.getText(
com.android.internal.R.string.mmiError);
phone.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 GSMPhone.
break;
case EVENT_USSD_CANCEL_COMPLETE:
phone.onMMIDone(this);
break;
}
}
//***** Private instance methods
private CharSequence getScString() {
if (sc != null) {
if (isServiceCodeCallBarring(sc)) {
return context.getText(com.android.internal.R.string.BaMmi);
} else if (isServiceCodeCallForwarding(sc)) {
return context.getText(com.android.internal.R.string.CfMmi);
} else if (sc.equals(SC_CLIP)) {
return context.getText(com.android.internal.R.string.ClipMmi);
} else if (sc.equals(SC_CLIR)) {
return context.getText(com.android.internal.R.string.ClirMmi);
} else if (sc.equals(SC_PWD)) {
return context.getText(com.android.internal.R.string.PwdMmi);
} else if (sc.equals(SC_WAIT)) {
return context.getText(com.android.internal.R.string.CwMmi);
} else if (isPinCommand()) {
return context.getText(com.android.internal.R.string.PinMmi);
}
}
return "";
}
private void
onSetComplete(AsyncResult ar){
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
if (ar.exception != null) {
state = State.FAILED;
if (ar.exception instanceof CommandException) {
CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
if (err == CommandException.Error.PASSWORD_INCORRECT) {
if (isPinCommand()) {
// look specifically for the PUK commands and adjust
// the message accordingly.
if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) {
sb.append(context.getText(
com.android.internal.R.string.badPuk));
} else {
sb.append(context.getText(
com.android.internal.R.string.badPin));
}
} else {
sb.append(context.getText(
com.android.internal.R.string.passwordIncorrect));
}
} else if (err == CommandException.Error.SIM_PUK2) {
sb.append(context.getText(
com.android.internal.R.string.badPin));
sb.append("\n");
sb.append(context.getText(
com.android.internal.R.string.needPuk2));
} else {
sb.append(context.getText(
com.android.internal.R.string.mmiError));
}
} else {
sb.append(context.getText(
com.android.internal.R.string.mmiError));
}
} else if (isActivate()) {
state = State.COMPLETE;
sb.append(context.getText(
com.android.internal.R.string.serviceEnabled));
// Record CLIR setting
if (sc.equals(SC_CLIR)) {
phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
}
} else if (isDeactivate()) {
state = State.COMPLETE;
sb.append(context.getText(
com.android.internal.R.string.serviceDisabled));
// Record CLIR setting
if (sc.equals(SC_CLIR)) {
phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
}
} else if (isRegister()) {
state = State.COMPLETE;
sb.append(context.getText(
com.android.internal.R.string.serviceRegistered));
} else if (isErasure()) {
state = State.COMPLETE;
sb.append(context.getText(
com.android.internal.R.string.serviceErased));
} else {
state = State.FAILED;
sb.append(context.getText(
com.android.internal.R.string.mmiError));
}
message = sb;
phone.onMMIDone(this);
}
private void
onGetClirComplete(AsyncResult ar) {
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
if (ar.exception != null) {
state = State.FAILED;
sb.append(context.getText(com.android.internal.R.string.mmiError));
} else {
int clirArgs[];
clirArgs = (int[])ar.result;
// the 'm' parameter from TS 27.007 7.7
switch (clirArgs[1]) {
case 0: // CLIR not provisioned
sb.append(context.getText(
com.android.internal.R.string.serviceNotProvisioned));
state = State.COMPLETE;
break;
case 1: // CLIR provisioned in permanent mode
sb.append(context.getText(
com.android.internal.R.string.CLIRPermanent));
state = State.COMPLETE;
break;
case 2: // unknown (e.g. no network, etc.)
sb.append(context.getText(
com.android.internal.R.string.mmiError));
state = State.FAILED;
break;
case 3: // CLIR temporary mode presentation restricted
// the 'n' parameter from TS 27.007 7.7
switch (clirArgs[0]) {
default:
case 0: // Default
sb.append(context.getText(
com.android.internal.R.string.CLIRDefaultOnNextCallOn));
break;
case 1: // CLIR invocation
sb.append(context.getText(
com.android.internal.R.string.CLIRDefaultOnNextCallOn));
break;
case 2: // CLIR suppression
sb.append(context.getText(
com.android.internal.R.string.CLIRDefaultOnNextCallOff));
break;
}
state = State.COMPLETE;
break;
case 4: // CLIR temporary mode presentation allowed
// the 'n' parameter from TS 27.007 7.7
switch (clirArgs[0]) {
default:
case 0: // Default
sb.append(context.getText(
com.android.internal.R.string.CLIRDefaultOffNextCallOff));
break;
case 1: // CLIR invocation
sb.append(context.getText(
com.android.internal.R.string.CLIRDefaultOffNextCallOn));
break;
case 2: // CLIR suppression
sb.append(context.getText(
com.android.internal.R.string.CLIRDefaultOffNextCallOff));
break;
}
state = State.COMPLETE;
break;
}
}
message = sb;
phone.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
*/
private CharSequence
serviceClassToCFString (int serviceClass) {
switch (serviceClass) {
case SERVICE_CLASS_VOICE:
return context.getText(com.android.internal.R.string.serviceClassVoice);
case SERVICE_CLASS_DATA:
return context.getText(com.android.internal.R.string.serviceClassData);
case SERVICE_CLASS_FAX:
return context.getText(com.android.internal.R.string.serviceClassFAX);
case SERVICE_CLASS_SMS:
return context.getText(com.android.internal.R.string.serviceClassSMS);
case SERVICE_CLASS_DATA_SYNC:
return context.getText(com.android.internal.R.string.serviceClassDataSync);
case SERVICE_CLASS_DATA_ASYNC:
return context.getText(com.android.internal.R.string.serviceClassDataAsync);
case SERVICE_CLASS_PACKET:
return context.getText(com.android.internal.R.string.serviceClassPacket);
case SERVICE_CLASS_PAD:
return context.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 = context.getText(
com.android.internal.R.string.cfTemplateForwardedTime);
} else {
template = context.getText(
com.android.internal.R.string.cfTemplateForwarded);
}
} else if (info.status == 0 && isEmptyOrNull(info.number)) {
template = context.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 = context.getText(
com.android.internal.R.string.cfTemplateRegisteredTime);
} else {
template = context.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);
phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled);
}
return TextUtils.replace(template, sources, destinations);
}
private void
onQueryCfComplete(AsyncResult ar) {
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
if (ar.exception != null) {
state = State.FAILED;
sb.append(context.getText(com.android.internal.R.string.mmiError));
} else {
CallForwardInfo infos[];
infos = (CallForwardInfo[]) ar.result;
if (infos.length == 0) {
// Assume the default is not active
sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
// Set unconditional CFF in SIM to false
phone.mSIMRecords.setVoiceCallForwardingFlag(1, false);
} 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 classs, 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);
}
state = State.COMPLETE;
}
message = sb;
phone.onMMIDone(this);
}
private void
onQueryComplete(AsyncResult ar) {
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
if (ar.exception != null) {
state = State.FAILED;
sb.append(context.getText(com.android.internal.R.string.mmiError));
} else {
int[] ints = (int[])ar.result;
if (ints.length != 0) {
if (ints[0] == 0) {
sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
} else if (sc.equals(SC_WAIT)) {
// Call Waiting includes additional data in the response.
sb.append(createQueryCallWaitingResultMessage(ints[1]));
} else if (isServiceCodeCallBarring(sc)) {
// ints[0] for Call Barring is a bit vector of services
sb.append(createQueryCallBarringResultMessage(ints[0]));
} else if (ints[0] == 1) {
// for all other services, treat it as a boolean
sb.append(context.getText(com.android.internal.R.string.serviceEnabled));
} else {
sb.append(context.getText(com.android.internal.R.string.mmiError));
}
} else {
sb.append(context.getText(com.android.internal.R.string.mmiError));
}
state = State.COMPLETE;
}
message = sb;
phone.onMMIDone(this);
}
private CharSequence
createQueryCallWaitingResultMessage(int serviceClass) {
StringBuilder sb =
new StringBuilder(context.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
createQueryCallBarringResultMessage(int serviceClass)
{
StringBuilder sb = new StringBuilder(context.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;
}
/***
* 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.
*/
}