blob: 3536755d39fb6d71b75b9330332644583c2d6221 [file] [log] [blame]
/*
* Copyright (C) 2011 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.app.Activity;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.provider.Telephony.Sms.Intents;
import android.telephony.PhoneNumberUtils;
import android.telephony.Rlog;
import android.telephony.SmsManager;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.cat.ComprehensionTlvTag;
import com.android.internal.telephony.uicc.IccIoResult;
import com.android.internal.telephony.uicc.IccUtils;
import com.android.internal.telephony.uicc.UsimServiceTable;
/**
* Handler for SMS-PP data download messages.
* See 3GPP TS 31.111 section 7.1.1
*/
public class UsimDataDownloadHandler extends Handler {
private static final String TAG = "UsimDataDownloadHandler";
/** BER-TLV tag for SMS-PP download. TS 31.111 section 9.1. */
private static final int BER_SMS_PP_DOWNLOAD_TAG = 0xd1;
/** Device identity value for UICC (destination). */
private static final int DEV_ID_UICC = 0x81;
/** Device identity value for network (source). */
private static final int DEV_ID_NETWORK = 0x83;
/** Message containing new SMS-PP message to process. */
private static final int EVENT_START_DATA_DOWNLOAD = 1;
/** Response to SMS-PP download envelope command. */
private static final int EVENT_SEND_ENVELOPE_RESPONSE = 2;
/** Result of writing SM to UICC (when SMS-PP service is not available). */
private static final int EVENT_WRITE_SMS_COMPLETE = 3;
private final CommandsInterface mCi;
public UsimDataDownloadHandler(CommandsInterface commandsInterface) {
mCi = commandsInterface;
}
/**
* Handle SMS-PP data download messages. Normally these are automatically handled by the
* radio, but we may have to deal with this type of SM arriving via the IMS stack. If the
* data download service is not enabled, try to write to the USIM as an SMS, and send the
* UICC response as the acknowledgment to the SMSC.
*
* @param ust the UsimServiceTable, to check if data download is enabled
* @param smsMessage the SMS message to process
* @return {@code Activity.RESULT_OK} on success; {@code RESULT_SMS_GENERIC_ERROR} on failure
*/
int handleUsimDataDownload(UsimServiceTable ust, SmsMessage smsMessage) {
// If we receive an SMS-PP message before the UsimServiceTable has been loaded,
// assume that the data download service is not present. This is very unlikely to
// happen because the IMS connection will not be established until after the ISIM
// records have been loaded, after the USIM service table has been loaded.
if (ust != null && ust.isAvailable(
UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) {
Rlog.d(TAG, "Received SMS-PP data download, sending to UICC.");
return startDataDownload(smsMessage);
} else {
Rlog.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC.");
String smsc = IccUtils.bytesToHexString(
PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
smsMessage.getServiceCenterAddress()));
mCi.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc,
IccUtils.bytesToHexString(smsMessage.getPdu()),
obtainMessage(EVENT_WRITE_SMS_COMPLETE));
return Activity.RESULT_OK; // acknowledge after response from write to USIM
}
}
/**
* Start an SMS-PP data download for the specified message. Can be called from a different
* thread than this Handler is running on.
*
* @param smsMessage the message to process
* @return {@code Activity.RESULT_OK} on success; {@code RESULT_SMS_GENERIC_ERROR} on failure
*/
public int startDataDownload(SmsMessage smsMessage) {
if (sendMessage(obtainMessage(EVENT_START_DATA_DOWNLOAD, smsMessage))) {
return Activity.RESULT_OK; // we will send SMS ACK/ERROR based on UICC response
} else {
Rlog.e(TAG, "startDataDownload failed to send message to start data download.");
return Intents.RESULT_SMS_GENERIC_ERROR;
}
}
private void handleDataDownload(SmsMessage smsMessage) {
int dcs = smsMessage.getDataCodingScheme();
int pid = smsMessage.getProtocolIdentifier();
byte[] pdu = smsMessage.getPdu(); // includes SC address
int scAddressLength = pdu[0] & 0xff;
int tpduIndex = scAddressLength + 1; // start of TPDU
int tpduLength = pdu.length - tpduIndex;
int bodyLength = getEnvelopeBodyLength(scAddressLength, tpduLength);
// Add 1 byte for SMS-PP download tag and 1-2 bytes for BER-TLV length.
// See ETSI TS 102 223 Annex C for encoding of length and tags.
int totalLength = bodyLength + 1 + (bodyLength > 127 ? 2 : 1);
byte[] envelope = new byte[totalLength];
int index = 0;
// SMS-PP download tag and length (assumed to be < 256 bytes).
envelope[index++] = (byte) BER_SMS_PP_DOWNLOAD_TAG;
if (bodyLength > 127) {
envelope[index++] = (byte) 0x81; // length 128-255 encoded as 0x81 + length
}
envelope[index++] = (byte) bodyLength;
// Device identities TLV
envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value());
envelope[index++] = (byte) 2;
envelope[index++] = (byte) DEV_ID_NETWORK;
envelope[index++] = (byte) DEV_ID_UICC;
// Address TLV (if present). Encoded length is assumed to be < 127 bytes.
if (scAddressLength != 0) {
envelope[index++] = (byte) ComprehensionTlvTag.ADDRESS.value();
envelope[index++] = (byte) scAddressLength;
System.arraycopy(pdu, 1, envelope, index, scAddressLength);
index += scAddressLength;
}
// SMS TPDU TLV. Length is assumed to be < 256 bytes.
envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.SMS_TPDU.value());
if (tpduLength > 127) {
envelope[index++] = (byte) 0x81; // length 128-255 encoded as 0x81 + length
}
envelope[index++] = (byte) tpduLength;
System.arraycopy(pdu, tpduIndex, envelope, index, tpduLength);
index += tpduLength;
// Verify that we calculated the payload size correctly.
if (index != envelope.length) {
Rlog.e(TAG, "startDataDownload() calculated incorrect envelope length, aborting.");
acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR);
return;
}
String encodedEnvelope = IccUtils.bytesToHexString(envelope);
mCi.sendEnvelopeWithStatus(encodedEnvelope, obtainMessage(
EVENT_SEND_ENVELOPE_RESPONSE, new int[]{ dcs, pid }));
}
/**
* Return the size in bytes of the envelope to send to the UICC, excluding the
* SMS-PP download tag byte and length byte(s). If the size returned is <= 127,
* the BER-TLV length will be encoded in 1 byte, otherwise 2 bytes are required.
*
* @param scAddressLength the length of the SMSC address, or zero if not present
* @param tpduLength the length of the TPDU from the SMS-PP message
* @return the number of bytes to allocate for the envelope command
*/
private static int getEnvelopeBodyLength(int scAddressLength, int tpduLength) {
// Add 4 bytes for device identities TLV + 1 byte for SMS TPDU tag byte
int length = tpduLength + 5;
// Add 1 byte for TPDU length, or 2 bytes if length > 127
length += (tpduLength > 127 ? 2 : 1);
// Add length of address tag, if present (+ 2 bytes for tag and length)
if (scAddressLength != 0) {
length = length + 2 + scAddressLength;
}
return length;
}
/**
* Handle the response to the ENVELOPE command.
* @param response UICC response encoded as hexadecimal digits. First two bytes are the
* UICC SW1 and SW2 status bytes.
*/
private void sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid) {
int sw1 = response.sw1;
int sw2 = response.sw2;
boolean success;
if ((sw1 == 0x90 && sw2 == 0x00) || sw1 == 0x91) {
Rlog.d(TAG, "USIM data download succeeded: " + response.toString());
success = true;
} else if (sw1 == 0x93 && sw2 == 0x00) {
Rlog.e(TAG, "USIM data download failed: Toolkit busy");
acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY);
return;
} else if (sw1 == 0x62 || sw1 == 0x63) {
Rlog.e(TAG, "USIM data download failed: " + response.toString());
success = false;
} else {
Rlog.e(TAG, "Unexpected SW1/SW2 response from UICC: " + response.toString());
success = false;
}
byte[] responseBytes = response.payload;
if (responseBytes == null || responseBytes.length == 0) {
if (success) {
mCi.acknowledgeLastIncomingGsmSms(true, 0, null);
} else {
acknowledgeSmsWithError(
CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR);
}
return;
}
byte[] smsAckPdu;
int index = 0;
if (success) {
smsAckPdu = new byte[responseBytes.length + 5];
smsAckPdu[index++] = 0x00; // TP-MTI, TP-UDHI
smsAckPdu[index++] = 0x07; // TP-PI: TP-PID, TP-DCS, TP-UDL present
} else {
smsAckPdu = new byte[responseBytes.length + 6];
smsAckPdu[index++] = 0x00; // TP-MTI, TP-UDHI
smsAckPdu[index++] = (byte)
CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR; // TP-FCS
smsAckPdu[index++] = 0x07; // TP-PI: TP-PID, TP-DCS, TP-UDL present
}
smsAckPdu[index++] = (byte) pid;
smsAckPdu[index++] = (byte) dcs;
if (is7bitDcs(dcs)) {
int septetCount = responseBytes.length * 8 / 7;
smsAckPdu[index++] = (byte) septetCount;
} else {
smsAckPdu[index++] = (byte) responseBytes.length;
}
System.arraycopy(responseBytes, 0, smsAckPdu, index, responseBytes.length);
mCi.acknowledgeIncomingGsmSmsWithPdu(success,
IccUtils.bytesToHexString(smsAckPdu), null);
}
private void acknowledgeSmsWithError(int cause) {
mCi.acknowledgeLastIncomingGsmSms(false, cause, null);
}
/**
* Returns whether the DCS is 7 bit. If so, set TP-UDL to the septet count of TP-UD;
* otherwise, set TP-UDL to the octet count of TP-UD.
* @param dcs the TP-Data-Coding-Scheme field from the original download SMS
* @return true if the DCS specifies 7 bit encoding; false otherwise
*/
private static boolean is7bitDcs(int dcs) {
// See 3GPP TS 23.038 section 4
return ((dcs & 0x8C) == 0x00) || ((dcs & 0xF4) == 0xF0);
}
/**
* Handle UICC envelope response and send SMS acknowledgement.
*
* @param msg the message to handle
*/
@Override
public void handleMessage(Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_START_DATA_DOWNLOAD:
handleDataDownload((SmsMessage) msg.obj);
break;
case EVENT_SEND_ENVELOPE_RESPONSE:
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
Rlog.e(TAG, "UICC Send Envelope failure, exception: " + ar.exception);
acknowledgeSmsWithError(
CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR);
return;
}
int[] dcsPid = (int[]) ar.userObj;
sendSmsAckForEnvelopeResponse((IccIoResult) ar.result, dcsPid[0], dcsPid[1]);
break;
case EVENT_WRITE_SMS_COMPLETE:
ar = (AsyncResult) msg.obj;
if (ar.exception == null) {
Rlog.d(TAG, "Successfully wrote SMS-PP message to UICC");
mCi.acknowledgeLastIncomingGsmSms(true, 0, null);
} else {
Rlog.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception);
mCi.acknowledgeLastIncomingGsmSms(false,
CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null);
}
break;
default:
Rlog.e(TAG, "Ignoring unexpected message, what=" + msg.what);
}
}
}