blob: 1e23e34a331241994168304c0b3c7b1054f0495f [file] [log] [blame]
/*
* Copyright (C) 2007 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.cat;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemProperties;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.IccCard;
import com.android.internal.telephony.IccFileHandler;
import com.android.internal.telephony.IccRecords;
import android.util.Config;
import java.io.ByteArrayOutputStream;
/**
* Enumeration for representing the tag value of COMPREHENSION-TLV objects. If
* you want to get the actual value, call {@link #value() value} method.
*
* {@hide}
*/
enum ComprehensionTlvTag {
COMMAND_DETAILS(0x01),
DEVICE_IDENTITIES(0x02),
RESULT(0x03),
DURATION(0x04),
ALPHA_ID(0x05),
USSD_STRING(0x0a),
TEXT_STRING(0x0d),
TONE(0x0e),
ITEM(0x0f),
ITEM_ID(0x10),
RESPONSE_LENGTH(0x11),
FILE_LIST(0x12),
HELP_REQUEST(0x15),
DEFAULT_TEXT(0x17),
EVENT_LIST(0x19),
ICON_ID(0x1e),
ITEM_ICON_ID_LIST(0x1f),
IMMEDIATE_RESPONSE(0x2b),
LANGUAGE(0x2d),
URL(0x31),
BROWSER_TERMINATION_CAUSE(0x34),
TEXT_ATTRIBUTE(0x50);
private int mValue;
ComprehensionTlvTag(int value) {
mValue = value;
}
/**
* Returns the actual value of this COMPREHENSION-TLV object.
*
* @return Actual tag value of this object
*/
public int value() {
return mValue;
}
public static ComprehensionTlvTag fromInt(int value) {
for (ComprehensionTlvTag e : ComprehensionTlvTag.values()) {
if (e.mValue == value) {
return e;
}
}
return null;
}
}
class RilMessage {
int mId;
Object mData;
ResultCode mResCode;
RilMessage(int msgId, String rawData) {
mId = msgId;
mData = rawData;
}
RilMessage(RilMessage other) {
this.mId = other.mId;
this.mData = other.mData;
this.mResCode = other.mResCode;
}
}
/**
* Class that implements SIM Toolkit Telephony Service. Interacts with the RIL
* and application.
*
* {@hide}
*/
public class CatService extends Handler implements AppInterface {
// Class members
private static IccRecords mIccRecords;
// Service members.
private static CatService sInstance;
private CommandsInterface mCmdIf;
private Context mContext;
private CatCmdMessage mCurrntCmd = null;
private CatCmdMessage mMenuCmd = null;
private RilMessageDecoder mMsgDecoder = null;
// Service constants.
static final int MSG_ID_SESSION_END = 1;
static final int MSG_ID_PROACTIVE_COMMAND = 2;
static final int MSG_ID_EVENT_NOTIFY = 3;
static final int MSG_ID_CALL_SETUP = 4;
static final int MSG_ID_REFRESH = 5;
static final int MSG_ID_RESPONSE = 6;
static final int MSG_ID_RIL_MSG_DECODED = 10;
// Events to signal SIM presence or absent in the device.
private static final int MSG_ID_ICC_RECORDS_LOADED = 20;
private static final int DEV_ID_KEYPAD = 0x01;
private static final int DEV_ID_DISPLAY = 0x02;
private static final int DEV_ID_EARPIECE = 0x03;
private static final int DEV_ID_UICC = 0x81;
private static final int DEV_ID_TERMINAL = 0x82;
private static final int DEV_ID_NETWORK = 0x83;
/* Intentionally private for singleton */
private CatService(CommandsInterface ci, IccRecords ir, Context context,
IccFileHandler fh, IccCard ic) {
if (ci == null || ir == null || context == null || fh == null
|| ic == null) {
throw new NullPointerException(
"Service: Input parameters must not be null");
}
mCmdIf = ci;
mContext = context;
// Get the RilMessagesDecoder for decoding the messages.
mMsgDecoder = RilMessageDecoder.getInstance(this, fh);
// Register ril events handling.
mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);
mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);
mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null);
mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null);
//mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null);
mIccRecords = ir;
// Register for SIM ready event.
mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);
mCmdIf.reportStkServiceIsRunning(null);
CatLog.d(this, "Is running");
}
public void dispose() {
mIccRecords.unregisterForRecordsLoaded(this);
mCmdIf.unSetOnCatSessionEnd(this);
mCmdIf.unSetOnCatProactiveCmd(this);
mCmdIf.unSetOnCatEvent(this);
mCmdIf.unSetOnCatCallSetUp(this);
this.removeCallbacksAndMessages(null);
}
protected void finalize() {
CatLog.d(this, "Service finalized");
}
private void handleRilMsg(RilMessage rilMsg) {
if (rilMsg == null) {
return;
}
// dispatch messages
CommandParams cmdParams = null;
switch (rilMsg.mId) {
case MSG_ID_EVENT_NOTIFY:
if (rilMsg.mResCode == ResultCode.OK) {
cmdParams = (CommandParams) rilMsg.mData;
if (cmdParams != null) {
handleProactiveCommand(cmdParams);
}
}
break;
case MSG_ID_PROACTIVE_COMMAND:
cmdParams = (CommandParams) rilMsg.mData;
if (cmdParams != null) {
if (rilMsg.mResCode == ResultCode.OK) {
handleProactiveCommand(cmdParams);
} else {
// for proactive commands that couldn't be decoded
// successfully respond with the code generated by the
// message decoder.
sendTerminalResponse(cmdParams.cmdDet, rilMsg.mResCode,
false, 0, null);
}
}
break;
case MSG_ID_REFRESH:
cmdParams = (CommandParams) rilMsg.mData;
if (cmdParams != null) {
handleProactiveCommand(cmdParams);
}
break;
case MSG_ID_SESSION_END:
handleSessionEnd();
break;
case MSG_ID_CALL_SETUP:
// prior event notify command supplied all the information
// needed for set up call processing.
break;
}
}
/**
* Handles RIL_UNSOL_STK_PROACTIVE_COMMAND unsolicited command from RIL.
* Sends valid proactive command data to the application using intents.
*
*/
private void handleProactiveCommand(CommandParams cmdParams) {
CatLog.d(this, cmdParams.getCommandType().name());
CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams);
switch (cmdParams.getCommandType()) {
case SET_UP_MENU:
if (removeMenu(cmdMsg.getMenu())) {
mMenuCmd = null;
} else {
mMenuCmd = cmdMsg;
}
sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
break;
case DISPLAY_TEXT:
// when application is not required to respond, send an immediate response.
if (!cmdMsg.geTextMessage().responseNeeded) {
sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
}
break;
case REFRESH:
// ME side only handles refresh commands which meant to remove IDLE
// MODE TEXT.
cmdParams.cmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT.value();
break;
case SET_UP_IDLE_MODE_TEXT:
sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
break;
case PROVIDE_LOCAL_INFORMATION:
sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
return;
case LAUNCH_BROWSER:
case SELECT_ITEM:
case GET_INPUT:
case GET_INKEY:
case SEND_DTMF:
case SEND_SMS:
case SEND_SS:
case SEND_USSD:
case PLAY_TONE:
case SET_UP_CALL:
// nothing to do on telephony!
break;
default:
CatLog.d(this, "Unsupported command");
return;
}
mCurrntCmd = cmdMsg;
Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
intent.putExtra("STK CMD", cmdMsg);
mContext.sendBroadcast(intent);
}
/**
* Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL.
*
*/
private void handleSessionEnd() {
CatLog.d(this, "SESSION END");
mCurrntCmd = mMenuCmd;
Intent intent = new Intent(AppInterface.CAT_SESSION_END_ACTION);
mContext.sendBroadcast(intent);
}
private void sendTerminalResponse(CommandDetails cmdDet,
ResultCode resultCode, boolean includeAdditionalInfo,
int additionalInfo, ResponseData resp) {
if (cmdDet == null) {
return;
}
ByteArrayOutputStream buf = new ByteArrayOutputStream();
Input cmdInput = null;
if (mCurrntCmd != null) {
cmdInput = mCurrntCmd.geInput();
}
// command details
int tag = ComprehensionTlvTag.COMMAND_DETAILS.value();
if (cmdDet.compRequired) {
tag |= 0x80;
}
buf.write(tag);
buf.write(0x03); // length
buf.write(cmdDet.commandNumber);
buf.write(cmdDet.typeOfCommand);
buf.write(cmdDet.commandQualifier);
// device identities
// According to TS102.223/TS31.111 section 6.8 Structure of
// TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
// the ME should set the CR(comprehension required) flag to
// comprehension not required.(CR=0)"
// Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
// the CR flag is not set.
tag = ComprehensionTlvTag.DEVICE_IDENTITIES.value();
buf.write(tag);
buf.write(0x02); // length
buf.write(DEV_ID_TERMINAL); // source device id
buf.write(DEV_ID_UICC); // destination device id
// result
tag = 0x80 | ComprehensionTlvTag.RESULT.value();
buf.write(tag);
int length = includeAdditionalInfo ? 2 : 1;
buf.write(length);
buf.write(resultCode.value());
// additional info
if (includeAdditionalInfo) {
buf.write(additionalInfo);
}
// Fill optional data for each corresponding command
if (resp != null) {
resp.format(buf);
} else {
encodeOptionalTags(cmdDet, resultCode, cmdInput, buf);
}
byte[] rawData = buf.toByteArray();
String hexString = IccUtils.bytesToHexString(rawData);
if (Config.LOGD) {
CatLog.d(this, "TERMINAL RESPONSE: " + hexString);
}
mCmdIf.sendTerminalResponse(hexString, null);
}
private void encodeOptionalTags(CommandDetails cmdDet,
ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf) {
switch (AppInterface.CommandType.fromInt(cmdDet.typeOfCommand)) {
case GET_INKEY:
// ETSI TS 102 384,27.22.4.2.8.4.2.
// If it is a response for GET_INKEY command and the response timeout
// occured, then add DURATION TLV for variable timeout case.
if ((resultCode.value() == ResultCode.NO_RESPONSE_FROM_USER.value()) &&
(cmdInput != null) && (cmdInput.duration != null)) {
getInKeyResponse(buf, cmdInput);
}
break;
case PROVIDE_LOCAL_INFORMATION:
if ((cmdDet.commandQualifier == CommandParamsFactory.LANGUAGE_SETTING) &&
(resultCode.value() == ResultCode.OK.value())) {
getPliResponse(buf);
}
break;
default:
CatLog.d(this, "encodeOptionalTags() Unsupported Cmd:" + cmdDet.typeOfCommand);
break;
}
}
private void getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput) {
int tag = ComprehensionTlvTag.DURATION.value();
buf.write(tag);
buf.write(0x02); // length
buf.write(cmdInput.duration.timeUnit.SECOND.value()); // Time (Unit,Seconds)
buf.write(cmdInput.duration.timeInterval); // Time Duration
}
private void getPliResponse(ByteArrayOutputStream buf) {
// Locale Language Setting
String lang = SystemProperties.get("persist.sys.language");
if (lang != null) {
// tag
int tag = ComprehensionTlvTag.LANGUAGE.value();
buf.write(tag);
ResponseData.writeLength(buf, lang.length());
buf.write(lang.getBytes(), 0, lang.length());
}
}
private void sendMenuSelection(int menuId, boolean helpRequired) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
// tag
int tag = BerTlv.BER_MENU_SELECTION_TAG;
buf.write(tag);
// length
buf.write(0x00); // place holder
// device identities
tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
buf.write(tag);
buf.write(0x02); // length
buf.write(DEV_ID_KEYPAD); // source device id
buf.write(DEV_ID_UICC); // destination device id
// item identifier
tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
buf.write(tag);
buf.write(0x01); // length
buf.write(menuId); // menu identifier chosen
// help request
if (helpRequired) {
tag = ComprehensionTlvTag.HELP_REQUEST.value();
buf.write(tag);
buf.write(0x00); // length
}
byte[] rawData = buf.toByteArray();
// write real length
int len = rawData.length - 2; // minus (tag + length)
rawData[1] = (byte) len;
String hexString = IccUtils.bytesToHexString(rawData);
mCmdIf.sendEnvelope(hexString, null);
}
private void eventDownload(int event, int sourceId, int destinationId,
byte[] additionalInfo, boolean oneShot) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
// tag
int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG;
buf.write(tag);
// length
buf.write(0x00); // place holder, assume length < 128.
// event list
tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value();
buf.write(tag);
buf.write(0x01); // length
buf.write(event); // event value
// device identities
tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
buf.write(tag);
buf.write(0x02); // length
buf.write(sourceId); // source device id
buf.write(destinationId); // destination device id
// additional information
if (additionalInfo != null) {
for (byte b : additionalInfo) {
buf.write(b);
}
}
byte[] rawData = buf.toByteArray();
// write real length
int len = rawData.length - 2; // minus (tag + length)
rawData[1] = (byte) len;
String hexString = IccUtils.bytesToHexString(rawData);
mCmdIf.sendEnvelope(hexString, null);
}
/**
* Used for instantiating/updating the Service from the GsmPhone or CdmaPhone constructor.
*
* @param ci CommandsInterface object
* @param ir IccRecords object
* @param context phone app context
* @param fh Icc file handler
* @param ic Icc card
* @return The only Service object in the system
*/
public static CatService getInstance(CommandsInterface ci, IccRecords ir,
Context context, IccFileHandler fh, IccCard ic) {
if (sInstance == null) {
if (ci == null || ir == null || context == null || fh == null
|| ic == null) {
return null;
}
HandlerThread thread = new HandlerThread("Cat Telephony service");
thread.start();
sInstance = new CatService(ci, ir, context, fh, ic);
CatLog.d(sInstance, "NEW sInstance");
} else if ((ir != null) && (mIccRecords != ir)) {
CatLog.d(sInstance, "Reinitialize the Service with SIMRecords");
mIccRecords = ir;
// re-Register for SIM ready event.
mIccRecords.registerForRecordsLoaded(sInstance, MSG_ID_ICC_RECORDS_LOADED, null);
CatLog.d(sInstance, "sr changed reinitialize and return current sInstance");
} else {
CatLog.d(sInstance, "Return current sInstance");
}
return sInstance;
}
/**
* Used by application to get an AppInterface object.
*
* @return The only Service object in the system
*/
public static AppInterface getInstance() {
return getInstance(null, null, null, null, null);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ID_SESSION_END:
case MSG_ID_PROACTIVE_COMMAND:
case MSG_ID_EVENT_NOTIFY:
case MSG_ID_REFRESH:
CatLog.d(this, "ril message arrived");
String data = null;
if (msg.obj != null) {
AsyncResult ar = (AsyncResult) msg.obj;
if (ar != null && ar.result != null) {
try {
data = (String) ar.result;
} catch (ClassCastException e) {
break;
}
}
}
mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
break;
case MSG_ID_CALL_SETUP:
mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null));
break;
case MSG_ID_ICC_RECORDS_LOADED:
break;
case MSG_ID_RIL_MSG_DECODED:
handleRilMsg((RilMessage) msg.obj);
break;
case MSG_ID_RESPONSE:
handleCmdResponse((CatResponseMessage) msg.obj);
break;
default:
throw new AssertionError("Unrecognized CAT command: " + msg.what);
}
}
public synchronized void onCmdResponse(CatResponseMessage resMsg) {
if (resMsg == null) {
return;
}
// queue a response message.
Message msg = this.obtainMessage(MSG_ID_RESPONSE, resMsg);
msg.sendToTarget();
}
private boolean validateResponse(CatResponseMessage resMsg) {
if (mCurrntCmd != null) {
return (resMsg.cmdDet.compareTo(mCurrntCmd.mCmdDet));
}
return false;
}
private boolean removeMenu(Menu menu) {
try {
if (menu.items.size() == 1 && menu.items.get(0) == null) {
return true;
}
} catch (NullPointerException e) {
CatLog.d(this, "Unable to get Menu's items size");
return true;
}
return false;
}
private void handleCmdResponse(CatResponseMessage resMsg) {
// Make sure the response details match the last valid command. An invalid
// response is a one that doesn't have a corresponding proactive command
// and sending it can "confuse" the baseband/ril.
// One reason for out of order responses can be UI glitches. For example,
// if the application launch an activity, and that activity is stored
// by the framework inside the history stack. That activity will be
// available for relaunch using the latest application dialog
// (long press on the home button). Relaunching that activity can send
// the same command's result again to the CatService and can cause it to
// get out of sync with the SIM.
if (!validateResponse(resMsg)) {
return;
}
ResponseData resp = null;
boolean helpRequired = false;
CommandDetails cmdDet = resMsg.getCmdDetails();
switch (resMsg.resCode) {
case HELP_INFO_REQUIRED:
helpRequired = true;
// fall through
case OK:
case PRFRMD_WITH_PARTIAL_COMPREHENSION:
case PRFRMD_WITH_MISSING_INFO:
case PRFRMD_WITH_ADDITIONAL_EFS_READ:
case PRFRMD_ICON_NOT_DISPLAYED:
case PRFRMD_MODIFIED_BY_NAA:
case PRFRMD_LIMITED_SERVICE:
case PRFRMD_WITH_MODIFICATION:
case PRFRMD_NAA_NOT_ACTIVE:
case PRFRMD_TONE_NOT_PLAYED:
switch (AppInterface.CommandType.fromInt(cmdDet.typeOfCommand)) {
case SET_UP_MENU:
helpRequired = resMsg.resCode == ResultCode.HELP_INFO_REQUIRED;
sendMenuSelection(resMsg.usersMenuSelection, helpRequired);
return;
case SELECT_ITEM:
resp = new SelectItemResponseData(resMsg.usersMenuSelection);
break;
case GET_INPUT:
case GET_INKEY:
Input input = mCurrntCmd.geInput();
if (!input.yesNo) {
// when help is requested there is no need to send the text
// string object.
if (!helpRequired) {
resp = new GetInkeyInputResponseData(resMsg.usersInput,
input.ucs2, input.packed);
}
} else {
resp = new GetInkeyInputResponseData(
resMsg.usersYesNoSelection);
}
break;
case DISPLAY_TEXT:
case LAUNCH_BROWSER:
break;
case SET_UP_CALL:
mCmdIf.handleCallSetupRequestFromSim(resMsg.usersConfirm, null);
// No need to send terminal response for SET UP CALL. The user's
// confirmation result is send back using a dedicated ril message
// invoked by the CommandInterface call above.
mCurrntCmd = null;
return;
}
break;
case NO_RESPONSE_FROM_USER:
case UICC_SESSION_TERM_BY_USER:
case BACKWARD_MOVE_BY_USER:
resp = null;
break;
default:
return;
}
sendTerminalResponse(cmdDet, resMsg.resCode, false, 0, resp);
mCurrntCmd = null;
}
}