SMSDispatcherpublic final class SMSDispatcher extends android.os.Handler
Fields Summary |
---|
private static final String | TAG | private static final int | DEFAULT_SMS_CHECK_PERIODDefault checking period for SMS sent without uesr permit | private static final int | DEFAULT_SMS_MAX_COUNTDefault number of SMS sent in checking period without uesr permit | private static final int | DEFAULT_SMS_TIMOUEOUTDefault timeout for SMS sent query | private static final String[] | RAW_PROJECTION | static final int | MAIL_SEND_SMS | static final int | EVENT_NEW_SMS | static final int | EVENT_SEND_SMS_COMPLETE | static final int | EVENT_SEND_RETRYRetry sending a previously failed SMS message | static final int | EVENT_NEW_SMS_STATUS_REPORTStatus report received | static final int | EVENT_SIM_FULLSIM storage is full | static final int | EVENT_POST_ALERTSMS confirm required | static final int | EVENT_SEND_CONFIRMED_SMSSend the user confirmed SMS | static final int | EVENT_ALERT_TIMEOUTAlert is timeout | private final GSMPhone | mPhone | private final com.android.internal.telephony.WapPushOverSms | mWapPush | private final android.content.Context | mContext | private final android.content.ContentResolver | mResolver | private final CommandsInterface | mCm | private final android.net.Uri | mRawUri | private static final int | MAX_SEND_RETRIESMaximum number of times to retry sending a failed SMS. | private static final int | SEND_RETRY_DELAYDelay before next send attempt on a failed SMS, in milliseconds. | private static final int | SINGLE_PART_SMSsingle part SMS | private static int | sConcatenatedRefMessage reference for a CONCATENATED_8_BIT_REFERENCE or
CONCATENATED_16_BIT_REFERENCE message set. Should be
incremented for each set of concatenated messages. | private SmsCounter | mCounter | private SmsTracker | mSTracker | private PowerManager.WakeLock | mWakeLockWake lock to ensure device stays awake while dispatching the SMS intent. | private final int | WAKE_LOCK_TIMEOUTHold the wake lock for 5 seconds, which should be enough time for
any receiver(s) to grab its own wake lock. | private final ArrayList | deliveryPendingListSent messages awaiting a delivery status report. | private DialogInterface.OnClickListener | mListener |
Constructors Summary |
---|
SMSDispatcher(GSMPhone phone)
mPhone = phone;
mWapPush = new WapPushOverSms(phone);
mContext = phone.getContext();
mResolver = mContext.getContentResolver();
mCm = phone.mCM;
mSTracker = null;
createWakelock();
int check_period = Settings.Gservices.getInt(mResolver,
Settings.Gservices.SMS_OUTGOING_CEHCK_INTERVAL_MS,
DEFAULT_SMS_CHECK_PERIOD);
int max_count = Settings.Gservices.getInt(mResolver,
Settings.Gservices.SMS_OUTGOING_CEHCK_MAX_COUNT,
DEFAULT_SMS_MAX_COUNT);
mCounter = new SmsCounter(max_count, check_period);
mCm.setOnNewSMS(this, EVENT_NEW_SMS, null);
mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
mCm.setOnSimSmsFull(this, EVENT_SIM_FULL, null);
// Don't always start message ref at 0.
sConcatenatedRef = new Random().nextInt(256);
|
Methods Summary |
---|
private void | createWakelock()
PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher");
mWakeLock.setReferenceCounted(true);
| void | dispatchMessage(android.telephony.gsm.SmsMessage sms)Dispatches an incoming SMS messages.
// If sms is null, means there was a parsing error.
// TODO: Should NAK this.
if (sms == null) {
return;
}
boolean handled = false;
// Special case the message waiting indicator messages
if (sms.isMWISetMessage()) {
mPhone.updateMessageWaitingIndicator(true);
if (sms.isMwiDontStore()) {
handled = true;
}
if (Config.LOGD) {
Log.d(TAG,
"Received voice mail indicator set SMS shouldStore="
+ !handled);
}
} else if (sms.isMWIClearMessage()) {
mPhone.updateMessageWaitingIndicator(false);
if (sms.isMwiDontStore()) {
handled = true;
}
if (Config.LOGD) {
Log.d(TAG,
"Received voice mail indicator clear SMS shouldStore="
+ !handled);
}
}
if (handled) {
return;
}
// Parse the headers to see if this is partial, or port addressed
int referenceNumber = -1;
int count = 0;
int sequence = 0;
int destPort = -1;
SmsHeader header = sms.getUserDataHeader();
if (header != null) {
for (SmsHeader.Element element : header.getElements()) {
try {
switch (element.getID()) {
case SmsHeader.CONCATENATED_8_BIT_REFERENCE: {
byte[] data = element.getData();
referenceNumber = data[0] & 0xff;
count = data[1] & 0xff;
sequence = data[2] & 0xff;
// Per TS 23.040, 9.2.3.24.1: If the count is zero, sequence
// is zero, or sequence > count, ignore the entire element
if (count == 0 || sequence == 0 || sequence > count) {
referenceNumber = -1;
}
break;
}
case SmsHeader.CONCATENATED_16_BIT_REFERENCE: {
byte[] data = element.getData();
referenceNumber = (data[0] & 0xff) * 256 + (data[1] & 0xff);
count = data[2] & 0xff;
sequence = data[3] & 0xff;
// Per TS 23.040, 9.2.3.24.8: If the count is zero, sequence
// is zero, or sequence > count, ignore the entire element
if (count == 0 || sequence == 0 || sequence > count) {
referenceNumber = -1;
}
break;
}
case SmsHeader.APPLICATION_PORT_ADDRESSING_16_BIT: {
byte[] data = element.getData();
destPort = (data[0] & 0xff) << 8;
destPort |= (data[1] & 0xff);
break;
}
}
} catch (ArrayIndexOutOfBoundsException e) {
Log.e(TAG, "Bad element in header", e);
return; // TODO: NACK the message or something, don't just discard.
}
}
}
if (referenceNumber == -1) {
// notify everyone of the message if it isn't partial
byte[][] pdus = new byte[1][];
pdus[0] = sms.getPdu();
if (destPort != -1) {
if (destPort == SmsHeader.PORT_WAP_PUSH) {
mWapPush.dispatchWapPdu(sms.getUserData());
}
// The message was sent to a port, so concoct a URI for it
dispatchPortAddressedPdus(pdus, destPort);
} else {
// It's a normal message, dispatch it
dispatchPdus(pdus);
}
} else {
// Process the message part
processMessagePart(sms, referenceNumber, sequence, count, destPort);
}
| private void | dispatchPdus(byte[][] pdus)Dispatches standard PDUs to interested applications
Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
intent.putExtra("pdus", pdus);
sendBroadcast(intent, "android.permission.RECEIVE_SMS");
| private void | dispatchPortAddressedPdus(byte[][] pdus, int port)Dispatches port addressed PDUs to interested applications
Uri uri = Uri.parse("sms://localhost:" + port);
Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
intent.putExtra("pdus", pdus);
sendBroadcast(intent, "android.permission.RECEIVE_SMS");
| private java.lang.String | getAppNameByIntent(android.app.PendingIntent intent)
Resources r = Resources.getSystem();
return (intent != null) ? intent.getTargetPackage()
: r.getString(R.string.sms_control_default_app_name);
| public void | handleMessage(android.os.Message msg)Handles events coming from the phone stack. Overridden from handler.
AsyncResult ar;
switch (msg.what) {
case EVENT_NEW_SMS:
// A new SMS has been received by the device
if (Config.LOGD) {
Log.d(TAG, "New SMS Message Received");
}
SmsMessage sms;
ar = (AsyncResult) msg.obj;
// FIXME unit test leaves cm == null. this should change
if (mCm != null) {
// FIXME only acknowledge on store
mCm.acknowledgeLastIncomingSMS(true, null);
}
if (ar.exception != null) {
Log.e(TAG, "Exception processing incoming SMS. Exception:" + ar.exception);
return;
}
sms = (SmsMessage) ar.result;
dispatchMessage(sms);
break;
case EVENT_SEND_SMS_COMPLETE:
// An outbound SMS has been sucessfully transferred, or failed.
handleSendComplete((AsyncResult) msg.obj);
break;
case EVENT_SEND_RETRY:
sendSms((SmsTracker) msg.obj);
break;
case EVENT_NEW_SMS_STATUS_REPORT:
handleStatusReport((AsyncResult)msg.obj);
break;
case EVENT_SIM_FULL:
handleSimFull();
break;
case EVENT_POST_ALERT:
handleReachSentLimit((SmsTracker)(msg.obj));
break;
case EVENT_ALERT_TIMEOUT:
((AlertDialog)(msg.obj)).dismiss();
msg.obj = null;
mSTracker = null;
break;
case EVENT_SEND_CONFIRMED_SMS:
if (mSTracker!=null) {
if (isMultipartTracker(mSTracker)) {
sendMultipartSms(mSTracker);
} else {
sendSms(mSTracker);
}
mSTracker = null;
}
break;
}
| private void | handleNotInService(int ss, com.android.internal.telephony.gsm.SMSDispatcher$SmsTracker tracker)Handles outbound message when the phone is not in service.
if (tracker.mSentIntent != null) {
try {
if (ss == ServiceState.STATE_POWER_OFF) {
tracker.mSentIntent.send(SmsManager.RESULT_ERROR_RADIO_OFF);
} else {
tracker.mSentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
}
} catch (CanceledException ex) {}
}
| private void | handleReachSentLimit(com.android.internal.telephony.gsm.SMSDispatcher$SmsTracker tracker)Post an alert while SMS needs user confirm.
An SmsTracker for the current message.
Resources r = Resources.getSystem();
String appName = getAppNameByIntent(tracker.mSentIntent);
AlertDialog d = new AlertDialog.Builder(mContext)
.setTitle(r.getString(R.string.sms_control_title))
.setMessage(appName + " " + r.getString(R.string.sms_control_message))
.setPositiveButton(r.getString(R.string.sms_control_yes), mListener)
.setNegativeButton(r.getString(R.string.sms_control_no), null)
.create();
d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
d.show();
mSTracker = tracker;
sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d),
DEFAULT_SMS_TIMOUEOUT);
| private void | handleSendComplete(android.os.AsyncResult ar)Called when SMS send completes. Broadcasts a sentIntent on success.
On failure, either sets up retries or broadcasts a sentIntent with
the failure in the result code.
SmsTracker tracker = (SmsTracker) ar.userObj;
PendingIntent sentIntent = tracker.mSentIntent;
if (ar.exception == null) {
if (Config.LOGD) {
Log.d(TAG, "SMS send complete. Broadcasting "
+ "intent: " + sentIntent);
}
if (tracker.mDeliveryIntent != null) {
// Expecting a status report. Add it to the list.
int messageRef = ((SmsResponse)ar.result).messageRef;
tracker.mMessageRef = messageRef;
deliveryPendingList.add(tracker);
}
if (sentIntent != null) {
try {
sentIntent.send(Activity.RESULT_OK);
} catch (CanceledException ex) {}
}
} else {
if (Config.LOGD) {
Log.d(TAG, "SMS send failed");
}
int ss = mPhone.getServiceState().getState();
if (ss != ServiceState.STATE_IN_SERVICE) {
handleNotInService(ss, tracker);
} else if ((((CommandException)(ar.exception)).getCommandError()
== CommandException.Error.SMS_FAIL_RETRY) &&
tracker.mRetryCount < MAX_SEND_RETRIES) {
// Retry after a delay if needed.
// TODO: According to TS 23.040, 9.2.3.6, we should resend
// with the same TP-MR as the failed message, and
// TP-RD set to 1. However, we don't have a means of
// knowing the MR for the failed message (EF_SMSstatus
// may or may not have the MR corresponding to this
// message, depending on the failure). Also, in some
// implementations this retry is handled by the baseband.
tracker.mRetryCount++;
Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
} else if (tracker.mSentIntent != null) {
// Done retrying; return an error to the app.
try {
tracker.mSentIntent.send(SmsManager.RESULT_ERROR_GENERIC_FAILURE);
} catch (CanceledException ex) {}
}
}
| private void | handleSimFull()Called when SIM_FULL message is received from the RIL. Notifies interested
parties that SIM storage for SMS messages is full.
// broadcast SIM_FULL intent
Intent intent = new Intent(Intents.SIM_FULL_ACTION);
sendBroadcast(intent, "android.permission.RECEIVE_SMS");
| private void | handleStatusReport(android.os.AsyncResult ar)Called when a status report is received. This should correspond to
a previously successful SEND.
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", SimUtils.hexStringToBytes(pduString));
try {
intent.send(mContext, Activity.RESULT_OK, fillIn);
} catch (CanceledException ex) {}
// Only expect to see one tracker matching this messageref
break;
}
}
}
if (mCm != null) {
mCm.acknowledgeLastIncomingSMS(true, null);
}
| private boolean | isMultipartTracker(com.android.internal.telephony.gsm.SMSDispatcher$SmsTracker tracker)Check if a SmsTracker holds multi-part Sms
HashMap map = tracker.mData;
return ( map.get("parts") != null);
| private void | processMessagePart(android.telephony.gsm.SmsMessage sms, int referenceNumber, int sequence, int count, int destinationPort)If this is the last part send the parts out to the application, otherwise
the part is stored for later processing.
// Lookup all other related parts
StringBuilder where = new StringBuilder("reference_number =");
where.append(referenceNumber);
where.append(" AND address = ?");
String[] whereArgs = new String[] {sms.getOriginatingAddress()};
byte[][] pdus = null;
Cursor cursor = null;
try {
cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
int cursorCount = cursor.getCount();
if (cursorCount != count - 1) {
// We don't have all the parts yet, store this one away
ContentValues values = new ContentValues();
values.put("date", new Long(sms.getTimestampMillis()));
values.put("pdu", HexDump.toHexString(sms.getPdu()));
values.put("address", sms.getOriginatingAddress());
values.put("reference_number", referenceNumber);
values.put("count", count);
values.put("sequence", sequence);
if (destinationPort != -1) {
values.put("destination_port", destinationPort);
}
mResolver.insert(mRawUri, values);
return;
}
// All the parts are in place, deal with them
int pduColumn = cursor.getColumnIndex("pdu");
int sequenceColumn = cursor.getColumnIndex("sequence");
pdus = new byte[count][];
for (int i = 0; i < cursorCount; i++) {
cursor.moveToNext();
int cursorSequence = (int)cursor.getLong(sequenceColumn);
pdus[cursorSequence - 1] = HexDump.hexStringToByteArray(
cursor.getString(pduColumn));
}
// This one isn't in the DB, so add it
pdus[sequence - 1] = sms.getPdu();
// Remove the parts from the database
mResolver.delete(mRawUri, where.toString(), whereArgs);
} catch (SQLException e) {
Log.e(TAG, "Can't access multipart SMS database", e);
return; // TODO: NACK the message or something, don't just discard.
} finally {
if (cursor != null) cursor.close();
}
// Dispatch the PDUs to applications
switch (destinationPort) {
case SmsHeader.PORT_WAP_PUSH: {
// Build up the data stream
ByteArrayOutputStream output = new ByteArrayOutputStream();
for (int i = 0; i < count; i++) {
SmsMessage msg = SmsMessage.createFromPdu(pdus[i]);
byte[] data = msg.getUserData();
output.write(data, 0, data.length);
}
// Handle the PUSH
mWapPush.dispatchWapPdu(output.toByteArray());
break;
}
case -1:
// The messages were not sent to a port
dispatchPdus(pdus);
break;
default:
// The messages were sent to a port, so concoct a URI for it
dispatchPortAddressedPdus(pdus, destinationPort);
break;
}
| private void | sendBroadcast(android.content.Intent intent, java.lang.String permission)
// Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
// receivers time to take their own wake locks.
mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
mContext.sendBroadcast(intent, permission);
| private void | sendMultipartSms(com.android.internal.telephony.gsm.SMSDispatcher$SmsTracker tracker)Send the multi-part SMS based on multipart Sms 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);
| void | sendMultipartText(java.lang.String destinationAddress, java.lang.String scAddress, java.util.ArrayList parts, java.util.ArrayList sentIntents, java.util.ArrayList deliveryIntents)Send a multi-part text based SMS.
PendingIntent sentIntent = null;
int ss = mPhone.getServiceState().getState();
if (ss == ServiceState.STATE_IN_SERVICE) {
// Only check SMS sending limit while in service
if (sentIntents != null && sentIntents.size() > 0) {
sentIntent = sentIntents.get(0);
}
String appName = getAppNameByIntent(sentIntent);
if ( !mCounter.check(appName, parts.size())) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("destination", destinationAddress);
map.put("scaddress", scAddress);
map.put("parts", parts);
map.put("sentIntents", sentIntents);
map.put("deliveryIntents", deliveryIntents);
SmsTracker multipartParameter = new SmsTracker(map, null, null);
sendMessage(obtainMessage(EVENT_POST_ALERT, multipartParameter));
return;
}
}
sendMultipartTextWithPermit(destinationAddress,
scAddress, parts, sentIntents, deliveryIntents);
| private void | sendMultipartTextWithPermit(java.lang.String destinationAddress, java.lang.String scAddress, java.util.ArrayList parts, java.util.ArrayList sentIntents, java.util.ArrayList deliveryIntents)Send a multi-part text based SMS which already passed SMS control check.
It is the working function for sendMultipartText().
PendingIntent sentIntent = null;
PendingIntent deliveryIntent = null;
// 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++) {
if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i);
}
SmsTracker tracker = new SmsTracker(null, sentIntent, null);
handleNotInService(ss, tracker);
}
return;
}
int ref = ++sConcatenatedRef & 0xff;
for (int i = 0, count = parts.size(); i < count; i++) {
// build SmsHeader
byte[] data = new byte[3];
data[0] = (byte) ref; // reference #, unique per message
data[1] = (byte) count; // total part count
data[2] = (byte) (i + 1); // 1-based sequence
SmsHeader header = new SmsHeader();
header.add(new SmsHeader.Element(SmsHeader.CONCATENATED_8_BIT_REFERENCE, data));
if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i);
}
if (deliveryIntents != null && deliveryIntents.size() > i) {
deliveryIntent = deliveryIntents.get(i);
}
SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
parts.get(i), deliveryIntent != null, header.toByteArray());
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("smsc", pdus.encodedScAddress);
map.put("pdu", pdus.encodedMessage);
SmsTracker tracker = new SmsTracker(map, sentIntent,
deliveryIntent);
sendSms(tracker);
}
| void | sendRawPdu(byte[] smsc, byte[] pdu, android.app.PendingIntent sentIntent, android.app.PendingIntent deliveryIntent)Send a SMS
if (pdu == null) {
if (sentIntent != null) {
try {
sentIntent.send(SmsManager.RESULT_ERROR_NULL_PDU);
} catch (CanceledException ex) {}
}
return;
}
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("smsc", smsc);
map.put("pdu", pdu);
SmsTracker tracker = new SmsTracker(map, sentIntent,
deliveryIntent);
int ss = mPhone.getServiceState().getState();
if (ss != ServiceState.STATE_IN_SERVICE) {
handleNotInService(ss, tracker);
} else {
String appName = getAppNameByIntent(sentIntent);
if (mCounter.check(appName, SINGLE_PART_SMS)) {
sendSms(tracker);
} else {
sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
}
}
| private void | sendSms(com.android.internal.telephony.gsm.SMSDispatcher$SmsTracker tracker)Send the message along to the radio.
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(SimUtils.bytesToHexString(smsc),
SimUtils.bytesToHexString(pdu), reply);
|
|