blob: fac36b0627d44f6c9696bfabc1c5db089cc3b66c [file] [log] [blame]
/*
* Copyright (C) 2008 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.cdma;
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Message;
import android.os.SystemProperties;
import android.preference.PreferenceManager;
import android.provider.Telephony.Sms.Intents;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsCbMessage;
import android.telephony.SmsManager;
import android.telephony.cdma.CdmaSmsCbProgramData;
import android.telephony.cdma.CdmaSmsCbProgramResults;
import android.telephony.Rlog;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsStorageMonitor;
import com.android.internal.telephony.SmsUsageMonitor;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.WspTypeDecoder;
import com.android.internal.telephony.cdma.sms.BearerData;
import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
import com.android.internal.telephony.cdma.sms.SmsEnvelope;
import com.android.internal.telephony.cdma.sms.UserData;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
final class CdmaSMSDispatcher extends SMSDispatcher {
private static final String TAG = "CdmaSMSDispatcher";
private static final boolean VDBG = false;
private byte[] mLastDispatchedSmsFingerprint;
private byte[] mLastAcknowledgedSmsFingerprint;
private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
CdmaSMSDispatcher(CDMAPhone phone, SmsStorageMonitor storageMonitor,
SmsUsageMonitor usageMonitor) {
super(phone, storageMonitor, usageMonitor);
mCi.setOnNewCdmaSms(this, EVENT_NEW_SMS, null);
}
@Override
public void dispose() {
mCi.unSetOnNewCdmaSms(this);
}
@Override
protected String getFormat() {
return android.telephony.SmsMessage.FORMAT_3GPP2;
}
private void handleCdmaStatusReport(SmsMessage sms) {
for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
SmsTracker tracker = deliveryPendingList.get(i);
if (tracker.mMessageRef == sms.mMessageRef) {
// Found it. Remove from list and broadcast.
deliveryPendingList.remove(i);
PendingIntent intent = tracker.mDeliveryIntent;
Intent fillIn = new Intent();
fillIn.putExtra("pdu", sms.getPdu());
fillIn.putExtra("format", android.telephony.SmsMessage.FORMAT_3GPP2);
try {
intent.send(mContext, Activity.RESULT_OK, fillIn);
} catch (CanceledException ex) {}
break; // Only expect to see one tracker matching this message.
}
}
}
/**
* Dispatch service category program data to the CellBroadcastReceiver app, which filters
* the broadcast alerts to display.
* @param sms the SMS message containing one or more
* {@link android.telephony.cdma.CdmaSmsCbProgramData} objects.
*/
private void handleServiceCategoryProgramData(SmsMessage sms) {
ArrayList<CdmaSmsCbProgramData> programDataList = sms.getSmsCbProgramData();
if (programDataList == null) {
Rlog.e(TAG, "handleServiceCategoryProgramData: program data list is null!");
return;
}
Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION);
intent.putExtra("sender", sms.getOriginatingAddress());
intent.putParcelableArrayListExtra("program_data", programDataList);
dispatch(intent, RECEIVE_SMS_PERMISSION, AppOpsManager.OP_RECEIVE_SMS, mScpResultsReceiver);
}
/** {@inheritDoc} */
@Override
public int dispatchMessage(SmsMessageBase smsb) {
// If sms is null, means there was a parsing error.
if (smsb == null) {
Rlog.e(TAG, "dispatchMessage: message is null");
return Intents.RESULT_SMS_GENERIC_ERROR;
}
String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
if (inEcm.equals("true")) {
return Activity.RESULT_OK;
}
if (mSmsReceiveDisabled) {
// Device doesn't support receiving SMS,
Rlog.d(TAG, "Received short message on device which doesn't support "
+ "receiving SMS. Ignored.");
return Intents.RESULT_SMS_HANDLED;
}
SmsMessage sms = (SmsMessage) smsb;
// Handle CMAS emergency broadcast messages.
if (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()) {
Rlog.d(TAG, "Broadcast type message");
SmsCbMessage message = sms.parseBroadcastSms();
if (message != null) {
dispatchBroadcastMessage(message);
}
return Intents.RESULT_SMS_HANDLED;
}
// See if we have a network duplicate SMS.
mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
if (mLastAcknowledgedSmsFingerprint != null &&
Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
return Intents.RESULT_SMS_HANDLED;
}
// Decode BD stream and set sms variables.
sms.parseSms();
int teleService = sms.getTeleService();
boolean handled = false;
if ((SmsEnvelope.TELESERVICE_VMN == teleService) ||
(SmsEnvelope.TELESERVICE_MWI == teleService)) {
// handling Voicemail
int voicemailCount = sms.getNumOfVoicemails();
Rlog.d(TAG, "Voicemail count=" + voicemailCount);
// Store the voicemail count in preferences.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
mContext);
SharedPreferences.Editor editor = sp.edit();
editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
editor.apply();
mPhone.setVoiceMessageWaiting(1, voicemailCount);
handled = true;
} else if (((SmsEnvelope.TELESERVICE_WMT == teleService) ||
(SmsEnvelope.TELESERVICE_WEMT == teleService)) &&
sms.isStatusReportMessage()) {
handleCdmaStatusReport(sms);
handled = true;
} else if (SmsEnvelope.TELESERVICE_SCPT == teleService) {
handleServiceCategoryProgramData(sms);
handled = true;
} else if ((sms.getUserData() == null)) {
if (VDBG) {
Rlog.d(TAG, "Received SMS without user data");
}
handled = true;
}
if (handled) {
return Intents.RESULT_SMS_HANDLED;
}
if (!mStorageMonitor.isStorageAvailable() &&
sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
// It's a storable message and there's no storage available. Bail.
// (See C.S0015-B v2.0 for a description of "Immediate Display"
// messages, which we represent as CLASS_0.)
return Intents.RESULT_SMS_OUT_OF_MEMORY;
}
if (SmsEnvelope.TELESERVICE_WAP == teleService) {
return processCdmaWapPdu(sms.getUserData(), sms.mMessageRef,
sms.getOriginatingAddress());
}
// Reject (NAK) any messages with teleservice ids that have
// not yet been handled and also do not correspond to the two
// kinds that are processed below.
if ((SmsEnvelope.TELESERVICE_WMT != teleService) &&
(SmsEnvelope.TELESERVICE_WEMT != teleService) &&
(SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) {
return Intents.RESULT_SMS_UNSUPPORTED;
}
return dispatchNormalMessage(smsb);
}
/**
* Processes inbound messages that are in the WAP-WDP PDU format. See
* wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
* WDP segments are gathered until a datagram completes and gets dispatched.
*
* @param pdu The WAP-WDP PDU segment
* @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
* {@link Activity#RESULT_OK} if the message has been broadcast
* to applications
*/
protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) {
int index = 0;
int msgType = (0xFF & pdu[index++]);
if (msgType != 0) {
Rlog.w(TAG, "Received a WAP SMS which is not WDP. Discard.");
return Intents.RESULT_SMS_HANDLED;
}
int totalSegments = (0xFF & pdu[index++]); // >= 1
int segment = (0xFF & pdu[index++]); // >= 0
if (segment >= totalSegments) {
Rlog.e(TAG, "WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1));
return Intents.RESULT_SMS_HANDLED;
}
// Only the first segment contains sourcePort and destination Port
int sourcePort = 0;
int destinationPort = 0;
if (segment == 0) {
//process WDP segment
sourcePort = (0xFF & pdu[index++]) << 8;
sourcePort |= 0xFF & pdu[index++];
destinationPort = (0xFF & pdu[index++]) << 8;
destinationPort |= 0xFF & pdu[index++];
// Some carriers incorrectly send duplicate port fields in omadm wap pushes.
// If configured, check for that here
if (mCheckForDuplicatePortsInOmadmWapPush) {
if (checkDuplicatePortOmadmWappush(pdu,index)) {
index = index + 4; // skip duplicate port fields
}
}
}
// Lookup all other related parts
Rlog.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address
+ ", src-port = " + sourcePort + ", dst-port = " + destinationPort
+ ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments);
// pass the user data portion of the PDU to the shared handler in SMSDispatcher
byte[] userData = new byte[pdu.length - index];
System.arraycopy(pdu, index, userData, 0, pdu.length - index);
return processMessagePart(userData, address, referenceNumber, segment, totalSegments,
0L, destinationPort, true);
}
/** {@inheritDoc} */
@Override
protected void sendData(String destAddr, String scAddr, int destPort,
byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, destPort, data, (deliveryIntent != null));
sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
}
/** {@inheritDoc} */
@Override
protected void sendText(String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, text, (deliveryIntent != null), null);
sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
}
/** {@inheritDoc} */
@Override
protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
boolean use7bitOnly) {
return SmsMessage.calculateLength(messageBody, use7bitOnly);
}
/** {@inheritDoc} */
@Override
protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
String message, SmsHeader smsHeader, int encoding,
PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
UserData uData = new UserData();
uData.payloadStr = message;
uData.userDataHeader = smsHeader;
if (encoding == SmsConstants.ENCODING_7BIT) {
uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
} else { // assume UTF-16
uData.msgEncoding = UserData.ENCODING_UNICODE_16;
}
uData.msgEncodingSet = true;
/* By setting the statusReportRequested bit only for the
* last message fragment, this will result in only one
* callback to the sender when that last fragment delivery
* has been acknowledged. */
SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
uData, (deliveryIntent != null) && lastPart);
sendSubmitPdu(submitPdu, sentIntent, deliveryIntent, destinationAddress);
}
protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
PendingIntent sentIntent, PendingIntent deliveryIntent, String destAddr) {
if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
if (sentIntent != null) {
try {
sentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
} catch (CanceledException ex) {}
}
if (VDBG) {
Rlog.d(TAG, "Block SMS in Emergency Callback mode");
}
return;
}
sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, destAddr);
}
/** {@inheritDoc} */
@Override
protected void sendSms(SmsTracker tracker) {
HashMap<String, Object> map = tracker.mData;
// byte smsc[] = (byte[]) map.get("smsc"); // unused for CDMA
byte pdu[] = (byte[]) map.get("pdu");
Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
mCi.sendCdmaSms(pdu, reply);
}
/** {@inheritDoc} */
@Override
protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
if (inEcm.equals("true")) {
return;
}
int causeCode = resultToCause(result);
mCi.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
if (causeCode == 0) {
mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
}
mLastDispatchedSmsFingerprint = null;
}
private static int resultToCause(int rc) {
switch (rc) {
case Activity.RESULT_OK:
case Intents.RESULT_SMS_HANDLED:
// Cause code is ignored on success.
return 0;
case Intents.RESULT_SMS_OUT_OF_MEMORY:
return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
case Intents.RESULT_SMS_UNSUPPORTED:
return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
case Intents.RESULT_SMS_GENERIC_ERROR:
default:
return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
}
}
/**
* Optional check to see if the received WapPush is an OMADM notification with erroneous
* extra port fields.
* - Some carriers make this mistake.
* ex: MSGTYPE-TotalSegments-CurrentSegment
* -SourcePortDestPort-SourcePortDestPort-OMADM PDU
* @param origPdu The WAP-WDP PDU segment
* @param index Current Index while parsing the PDU.
* @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
* False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
*/
private static boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) {
index += 4;
byte[] omaPdu = new byte[origPdu.length - index];
System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu);
int wspIndex = 2;
// Process header length field
if (pduDecoder.decodeUintvarInteger(wspIndex) == false) {
return false;
}
wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field
// Process content type field
if (pduDecoder.decodeContentType(wspIndex) == false) {
return false;
}
String mimeType = pduDecoder.getValueString();
if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI)) {
return true;
}
return false;
}
// Receiver for Service Category Program Data results.
// We already ACK'd the original SCPD SMS, so this sends a new response SMS.
// TODO: handle retries if the RIL returns an error.
private final BroadcastReceiver mScpResultsReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int rc = getResultCode();
boolean success = (rc == Activity.RESULT_OK) || (rc == Intents.RESULT_SMS_HANDLED);
if (!success) {
Rlog.e(TAG, "SCP results error: result code = " + rc);
return;
}
Bundle extras = getResultExtras(false);
if (extras == null) {
Rlog.e(TAG, "SCP results error: missing extras");
return;
}
String sender = extras.getString("sender");
if (sender == null) {
Rlog.e(TAG, "SCP results error: missing sender extra.");
return;
}
ArrayList<CdmaSmsCbProgramResults> results
= extras.getParcelableArrayList("results");
if (results == null) {
Rlog.e(TAG, "SCP results error: missing results extra.");
return;
}
BearerData bData = new BearerData();
bData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
bData.messageId = SmsMessage.getNextMessageId();
bData.serviceCategoryProgramResults = results;
byte[] encodedBearerData = BearerData.encode(bData);
ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
DataOutputStream dos = new DataOutputStream(baos);
try {
dos.writeInt(SmsEnvelope.TELESERVICE_SCPT);
dos.writeInt(0); //servicePresent
dos.writeInt(0); //serviceCategory
CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
PhoneNumberUtils.cdmaCheckAndProcessPlusCode(sender));
dos.write(destAddr.digitMode);
dos.write(destAddr.numberMode);
dos.write(destAddr.ton); // number_type
dos.write(destAddr.numberPlan);
dos.write(destAddr.numberOfDigits);
dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
// Subaddress is not supported.
dos.write(0); //subaddressType
dos.write(0); //subaddr_odd
dos.write(0); //subaddr_nbr_of_digits
dos.write(encodedBearerData.length);
dos.write(encodedBearerData, 0, encodedBearerData.length);
// Ignore the RIL response. TODO: implement retry if SMS send fails.
mCi.sendCdmaSms(baos.toByteArray(), null);
} catch (IOException e) {
Rlog.e(TAG, "exception creating SCP results PDU", e);
} finally {
try {
dos.close();
} catch (IOException ignored) {
}
}
}
};
}