blob: 0ca31488cc34929355c7d4fe8c255fcd6746f4fc [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.app.Activity;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Intent;
import android.os.AsyncResult;
import android.os.Message;
import android.provider.Telephony.Sms.Intents;
import android.telephony.ServiceState;
import android.util.Config;
import android.util.Log;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.gsm.SmsMessage;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import java.util.ArrayList;
import java.util.HashMap;
import static android.telephony.SmsMessage.MessageClass;
final class GsmSMSDispatcher extends SMSDispatcher {
private static final String TAG = "GSM";
private GSMPhone mGsmPhone;
GsmSMSDispatcher(GSMPhone phone) {
super(phone);
mGsmPhone = phone;
}
/**
* Called when a status report is received. This should correspond to
* a previously successful SEND.
*
* @param ar AsyncResult passed into the message handler. ar.result should
* be a String representing the status report PDU, as ASCII hex.
*/
protected void handleStatusReport(AsyncResult ar) {
String pduString = (String) ar.result;
SmsMessage sms = SmsMessage.newFromCDS(pduString);
if (sms != null) {
int messageRef = sms.messageRef;
for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
SmsTracker tracker = deliveryPendingList.get(i);
if (tracker.mMessageRef == messageRef) {
// Found it. Remove from list and broadcast.
deliveryPendingList.remove(i);
PendingIntent intent = tracker.mDeliveryIntent;
Intent fillIn = new Intent();
fillIn.putExtra("pdu", IccUtils.hexStringToBytes(pduString));
try {
intent.send(mContext, Activity.RESULT_OK, fillIn);
} catch (CanceledException ex) {}
// Only expect to see one tracker matching this messageref
break;
}
}
}
acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null);
}
/** {@inheritDoc} */
protected int dispatchMessage(SmsMessageBase smsb) {
// If sms is null, means there was a parsing error.
if (smsb == null) {
return Intents.RESULT_SMS_GENERIC_ERROR;
}
SmsMessage sms = (SmsMessage) smsb;
boolean handled = false;
// Special case the message waiting indicator messages
if (sms.isMWISetMessage()) {
mGsmPhone.updateMessageWaitingIndicator(true);
handled |= sms.isMwiDontStore();
if (Config.LOGD) {
Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
}
} else if (sms.isMWIClearMessage()) {
mGsmPhone.updateMessageWaitingIndicator(false);
handled |= sms.isMwiDontStore();
if (Config.LOGD) {
Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
}
}
if (handled) {
return Intents.RESULT_SMS_HANDLED;
}
if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) {
// It's a storable message and there's no storage available. Bail.
// (See TS 23.038 for a description of class 0 messages.)
return Intents.RESULT_SMS_OUT_OF_MEMORY;
}
SmsHeader smsHeader = sms.getUserDataHeader();
// See if message is partial or port addressed.
if ((smsHeader == null) || (smsHeader.concatRef == null)) {
// Message is not partial (not part of concatenated sequence).
byte[][] pdus = new byte[1][];
pdus[0] = sms.getPdu();
if (smsHeader != null && smsHeader.portAddrs != null) {
if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
return mWapPush.dispatchWapPdu(sms.getUserData());
} else {
// The message was sent to a port, so concoct a URI for it.
dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
}
} else {
// Normal short and non-port-addressed message, dispatch it.
dispatchPdus(pdus);
}
return Activity.RESULT_OK;
} else {
// Process the message part.
return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
}
}
/** {@inheritDoc} */
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));
sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
}
/** {@inheritDoc} */
protected void sendText(String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, text, (deliveryIntent != null));
sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
}
/** {@inheritDoc} */
protected void sendMultipartText(String destinationAddress, String scAddress,
ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
ArrayList<PendingIntent> deliveryIntents) {
int refNumber = getNextConcatenatedRef() & 0x00FF;
for (int i = 0, msgCount = parts.size(); i < msgCount; i++) {
SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
concatRef.refNumber = refNumber;
concatRef.seqNumber = i + 1; // 1-based sequence
concatRef.msgCount = msgCount;
// TODO: We currently set this to true since our messaging app will never
// send more than 255 parts (it converts the message to MMS well before that).
// However, we should support 3rd party messaging apps that might need 16-bit
// references
// Note: It's not sufficient to just flip this bit to true; it will have
// ripple effects (several calculations assume 8-bit ref).
concatRef.isEightBits = true;
SmsHeader smsHeader = new SmsHeader();
smsHeader.concatRef = concatRef;
PendingIntent sentIntent = null;
if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i);
}
PendingIntent deliveryIntent = null;
if (deliveryIntents != null && deliveryIntents.size() > i) {
deliveryIntent = deliveryIntents.get(i);
}
SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader));
sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent);
}
}
/**
* Send a multi-part text based SMS which already passed SMS control check.
*
* It is the working function for sendMultipartText().
*
* @param destinationAddress the address to send the message to
* @param scAddress is the service center address or null to use
* the current default SMSC
* @param parts an <code>ArrayList</code> of strings that, in order,
* comprise the original message
* @param sentIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been sent.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:
* <code>RESULT_ERROR_GENERIC_FAILURE</code>
* <code>RESULT_ERROR_RADIO_OFF</code>
* <code>RESULT_ERROR_NULL_PDU</code>.
* @param deliveryIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been delivered
* to the recipient. The raw pdu of the status report is in the
* extended data ("pdu").
*/
private void sendMultipartTextWithPermit(String destinationAddress,
String scAddress, ArrayList<String> parts,
ArrayList<PendingIntent> sentIntents,
ArrayList<PendingIntent> deliveryIntents) {
// check if in service
int ss = mPhone.getServiceState().getState();
if (ss != ServiceState.STATE_IN_SERVICE) {
for (int i = 0, count = parts.size(); i < count; i++) {
PendingIntent sentIntent = null;
if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i);
}
SmsTracker tracker = SmsTrackerFactory(null, sentIntent, null);
handleNotInService(ss, tracker);
}
return;
}
int refNumber = getNextConcatenatedRef() & 0x00FF;
for (int i = 0, msgCount = parts.size(); i < msgCount; i++) {
SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
concatRef.refNumber = refNumber;
concatRef.seqNumber = i + 1; // 1-based sequence
concatRef.msgCount = msgCount;
concatRef.isEightBits = false;
SmsHeader smsHeader = new SmsHeader();
smsHeader.concatRef = concatRef;
PendingIntent sentIntent = null;
if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i);
}
PendingIntent deliveryIntent = null;
if (deliveryIntents != null && deliveryIntents.size() > i) {
deliveryIntent = deliveryIntents.get(i);
}
SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader));
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("smsc", pdus.encodedScAddress);
map.put("pdu", pdus.encodedMessage);
SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent);
sendSms(tracker);
}
}
/** {@inheritDoc} */
protected void sendSms(SmsTracker tracker) {
HashMap map = tracker.mData;
byte smsc[] = (byte[]) map.get("smsc");
byte pdu[] = (byte[]) map.get("pdu");
Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
mCm.sendSMS(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), reply);
}
/**
* Send the multi-part SMS based on multipart Sms tracker
*
* @param tracker holds the multipart Sms tracker ready to be sent
*/
protected void sendMultipartSms (SmsTracker tracker) {
ArrayList<String> parts;
ArrayList<PendingIntent> sentIntents;
ArrayList<PendingIntent> deliveryIntents;
HashMap map = tracker.mData;
String destinationAddress = (String) map.get("destination");
String scAddress = (String) map.get("scaddress");
parts = (ArrayList<String>) map.get("parts");
sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents");
deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents");
sendMultipartTextWithPermit(destinationAddress,
scAddress, parts, sentIntents, deliveryIntents);
}
/** {@inheritDoc} */
protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
// FIXME unit test leaves cm == null. this should change
if (mCm != null) {
mCm.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
}
}
/** {@inheritDoc} */
protected void activateCellBroadcastSms(int activate, Message response) {
// Unless CBS is implemented for GSM, this point should be unreachable.
Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
response.recycle();
}
/** {@inheritDoc} */
protected void getCellBroadcastSmsConfig(Message response){
// Unless CBS is implemented for GSM, this point should be unreachable.
Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
response.recycle();
}
/** {@inheritDoc} */
protected void setCellBroadcastConfig(int[] configValuesArray, Message response) {
// Unless CBS is implemented for GSM, this point should be unreachable.
Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
response.recycle();
}
private 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.GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED;
case Intents.RESULT_SMS_GENERIC_ERROR:
default:
return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR;
}
}
}