2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.internal.telephony;
19 import android.app.Activity;
20 import android.app.PendingIntent;
21 import android.app.AlertDialog;
22 import android.app.PendingIntent.CanceledException;
23 import android.content.BroadcastReceiver;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.DialogInterface;
29 import android.content.IntentFilter;
30 import android.content.res.Resources;
31 import android.database.Cursor;
32 import android.database.SQLException;
33 import android.net.Uri;
34 import android.os.AsyncResult;
35 import android.os.Environment;
36 import android.os.Handler;
37 import android.os.Message;
38 import android.os.PowerManager;
39 import android.os.StatFs;
40 import android.os.SystemProperties;
41 import android.provider.Telephony;
42 import android.provider.Telephony.Sms.Intents;
43 import android.provider.Settings;
44 import android.telephony.SmsMessage;
45 import android.telephony.ServiceState;
46 import android.util.Config;
47 import android.util.Log;
48 import android.view.WindowManager;
50 import com.android.internal.util.HexDump;
52 import java.io.ByteArrayOutputStream;
53 import java.util.ArrayList;
54 import java.util.HashMap;
55 import java.util.Random;
57 import com.android.internal.R;
59 import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
60 import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE;
61 import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU;
62 import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF;
63 import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
64 import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
67 public abstract class SMSDispatcher extends Handler {
68 private static final String TAG = "SMS";
69 private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg";
71 /** Default checking period for SMS sent without user permit */
72 private static final int DEFAULT_SMS_CHECK_PERIOD = 3600000;
74 /** Default number of SMS sent in checking period without user permit */
75 private static final int DEFAULT_SMS_MAX_COUNT = 100;
77 /** Default timeout for SMS sent query */
78 private static final int DEFAULT_SMS_TIMEOUT = 6000;
80 protected static final String[] RAW_PROJECTION = new String[] {
86 static final protected int EVENT_NEW_SMS = 1;
88 static final protected int EVENT_SEND_SMS_COMPLETE = 2;
90 /** Retry sending a previously failed SMS message */
91 static final protected int EVENT_SEND_RETRY = 3;
93 /** Status report received */
94 static final protected int EVENT_NEW_SMS_STATUS_REPORT = 5;
96 /** SIM/RUIM storage is full */
97 static final protected int EVENT_ICC_FULL = 6;
99 /** SMS confirm required */
100 static final protected int EVENT_POST_ALERT = 7;
102 /** Send the user confirmed SMS */
103 static final protected int EVENT_SEND_CONFIRMED_SMS = 8;
105 /** Alert is timeout */
106 static final protected int EVENT_ALERT_TIMEOUT = 9;
108 /** Stop the sending */
109 static final protected int EVENT_STOP_SENDING = 10;
111 /** Memory status reporting is acknowledged by RIL */
112 static final protected int EVENT_REPORT_MEMORY_STATUS_DONE = 11;
115 static final protected int EVENT_RADIO_ON = 12;
117 /** New broadcast SMS */
118 static final protected int EVENT_NEW_BROADCAST_SMS = 13;
120 protected Phone mPhone;
121 protected Context mContext;
122 protected ContentResolver mResolver;
123 protected CommandsInterface mCm;
125 protected final WapPushOverSms mWapPush;
127 protected final Uri mRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
129 /** Maximum number of times to retry sending a failed SMS. */
130 private static final int MAX_SEND_RETRIES = 3;
131 /** Delay before next send attempt on a failed SMS, in milliseconds. */
132 private static final int SEND_RETRY_DELAY = 2000;
133 /** single part SMS */
134 private static final int SINGLE_PART_SMS = 1;
135 /** Message sending queue limit */
136 private static final int MO_MSG_QUEUE_LIMIT = 5;
139 * Message reference for a CONCATENATED_8_BIT_REFERENCE or
140 * CONCATENATED_16_BIT_REFERENCE message set. Should be
141 * incremented for each set of concatenated messages.
143 private static int sConcatenatedRef;
145 private SmsCounter mCounter;
147 private ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT);
149 /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
150 private PowerManager.WakeLock mWakeLock;
153 * Hold the wake lock for 5 seconds, which should be enough time for
154 * any receiver(s) to grab its own wake lock.
156 private final int WAKE_LOCK_TIMEOUT = 5000;
158 protected boolean mStorageAvailable = true;
159 protected boolean mReportMemoryStatusPending = false;
161 /* Flags indicating whether the current device allows sms service */
162 protected boolean mSmsCapable = true;
163 protected boolean mSmsReceiveDisabled;
164 protected boolean mSmsSendDisabled;
166 protected static int mRemainingMessages = -1;
168 protected static int getNextConcatenatedRef() {
169 sConcatenatedRef += 1;
170 return sConcatenatedRef;
174 * Implement the per-application based SMS control, which only allows
175 * a limit on the number of SMS/MMS messages an app can send in checking
178 private class SmsCounter {
179 private int mCheckPeriod;
180 private int mMaxAllowed;
181 private HashMap<String, ArrayList<Long>> mSmsStamp;
185 * @param mMax is the number of SMS allowed without user permit
186 * @param mPeriod is the checking period
188 SmsCounter(int mMax, int mPeriod) {
190 mCheckPeriod = mPeriod;
191 mSmsStamp = new HashMap<String, ArrayList<Long>> ();
195 * Check to see if an application allow to send new SMS messages
197 * @param appName is the application sending sms
198 * @param smsWaiting is the number of new sms wants to be sent
199 * @return true if application is allowed to send the requested number
200 * of new sms messages
202 boolean check(String appName, int smsWaiting) {
203 if (!mSmsStamp.containsKey(appName)) {
204 mSmsStamp.put(appName, new ArrayList<Long>());
207 return isUnderLimit(mSmsStamp.get(appName), smsWaiting);
210 private boolean isUnderLimit(ArrayList<Long> sent, int smsWaiting) {
211 Long ct = System.currentTimeMillis();
213 Log.d(TAG, "SMS send size=" + sent.size() + "time=" + ct);
215 while (sent.size() > 0 && (ct - sent.get(0)) > mCheckPeriod ) {
220 if ( (sent.size() + smsWaiting) <= mMaxAllowed) {
221 for (int i = 0; i < smsWaiting; i++ ) {
230 protected SMSDispatcher(PhoneBase phone) {
232 mWapPush = new WapPushOverSms(phone, this);
233 mContext = phone.getContext();
234 mResolver = mContext.getContentResolver();
239 int check_period = Settings.Secure.getInt(mResolver,
240 Settings.Secure.SMS_OUTGOING_CHECK_INTERVAL_MS,
241 DEFAULT_SMS_CHECK_PERIOD);
242 int max_count = Settings.Secure.getInt(mResolver,
243 Settings.Secure.SMS_OUTGOING_CHECK_MAX_COUNT,
244 DEFAULT_SMS_MAX_COUNT);
245 mCounter = new SmsCounter(max_count, check_period);
247 mCm.setOnNewSMS(this, EVENT_NEW_SMS, null);
248 mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
249 mCm.setOnIccSmsFull(this, EVENT_ICC_FULL, null);
250 mCm.registerForOn(this, EVENT_RADIO_ON, null);
252 // Don't always start message ref at 0.
253 sConcatenatedRef = new Random().nextInt(256);
255 // Register for device storage intents. Use these to notify the RIL
256 // that storage for SMS is or is not available.
257 IntentFilter filter = new IntentFilter();
258 filter.addAction(Intent.ACTION_DEVICE_STORAGE_FULL);
259 filter.addAction(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
260 mContext.registerReceiver(mResultReceiver, filter);
262 mSmsCapable = mContext.getResources().getBoolean(
263 com.android.internal.R.bool.config_sms_capable);
264 mSmsReceiveDisabled = !SystemProperties.getBoolean(
265 TelephonyProperties.PROPERTY_SMS_RECEIVE, mSmsCapable);
266 mSmsSendDisabled = !SystemProperties.getBoolean(
267 TelephonyProperties.PROPERTY_SMS_SEND, mSmsCapable);
268 Log.d(TAG, "SMSDispatcher: ctor mSmsCapable=" + mSmsCapable
269 + " mSmsReceiveDisabled=" + mSmsReceiveDisabled
270 + " mSmsSendDisabled=" + mSmsSendDisabled);
273 public void dispose() {
274 mCm.unSetOnNewSMS(this);
275 mCm.unSetOnSmsStatus(this);
276 mCm.unSetOnIccSmsFull(this);
277 mCm.unregisterForOn(this);
281 protected void finalize() {
282 Log.d(TAG, "SMSDispatcher finalized");
286 /* TODO: Need to figure out how to keep track of status report routing in a
287 * persistent manner. If the phone process restarts (reboot or crash),
288 * we will lose this list and any status reports that come in after
291 /** Sent messages awaiting a delivery status report. */
292 protected final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>();
295 * Handles events coming from the phone stack. Overridden from handler.
297 * @param msg the message to handle
300 public void handleMessage(Message msg) {
305 // A new SMS has been received by the device
307 Log.d(TAG, "New SMS Message Received");
312 ar = (AsyncResult) msg.obj;
314 if (ar.exception != null) {
315 Log.e(TAG, "Exception processing incoming SMS. Exception:" + ar.exception);
319 sms = (SmsMessage) ar.result;
321 int result = dispatchMessage(sms.mWrappedSmsMessage);
322 if (result != Activity.RESULT_OK) {
323 // RESULT_OK means that message was broadcast for app(s) to handle.
324 // Any other result, we should ack here.
325 boolean handled = (result == Intents.RESULT_SMS_HANDLED);
326 notifyAndAcknowledgeLastIncomingSms(handled, result, null);
328 } catch (RuntimeException ex) {
329 Log.e(TAG, "Exception dispatching message", ex);
330 notifyAndAcknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null);
335 case EVENT_SEND_SMS_COMPLETE:
336 // An outbound SMS has been successfully transferred, or failed.
337 handleSendComplete((AsyncResult) msg.obj);
340 case EVENT_SEND_RETRY:
341 sendSms((SmsTracker) msg.obj);
344 case EVENT_NEW_SMS_STATUS_REPORT:
345 handleStatusReport((AsyncResult)msg.obj);
352 case EVENT_POST_ALERT:
353 handleReachSentLimit((SmsTracker)(msg.obj));
356 case EVENT_ALERT_TIMEOUT:
357 ((AlertDialog)(msg.obj)).dismiss();
359 if (mSTrackers.isEmpty() == false) {
361 SmsTracker sTracker = mSTrackers.remove(0);
362 sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
363 } catch (CanceledException ex) {
364 Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
368 Log.d(TAG, "EVENT_ALERT_TIMEOUT, message stop sending");
372 case EVENT_SEND_CONFIRMED_SMS:
373 if (mSTrackers.isEmpty() == false) {
374 SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
375 if (isMultipartTracker(sTracker)) {
376 sendMultipartSms(sTracker);
380 removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
384 case EVENT_STOP_SENDING:
385 if (mSTrackers.isEmpty() == false) {
386 // Remove the latest one.
388 SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
389 sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
390 } catch (CanceledException ex) {
391 Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
393 removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
397 case EVENT_REPORT_MEMORY_STATUS_DONE:
398 ar = (AsyncResult)msg.obj;
399 if (ar.exception != null) {
400 mReportMemoryStatusPending = true;
401 Log.v(TAG, "Memory status report to modem pending : mStorageAvailable = "
402 + mStorageAvailable);
404 mReportMemoryStatusPending = false;
409 if (mReportMemoryStatusPending) {
410 Log.v(TAG, "Sending pending memory status report : mStorageAvailable = "
411 + mStorageAvailable);
412 mCm.reportSmsMemoryStatus(mStorageAvailable,
413 obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
417 case EVENT_NEW_BROADCAST_SMS:
418 handleBroadcastSms((AsyncResult)msg.obj);
423 private void createWakelock() {
424 PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
425 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher");
426 mWakeLock.setReferenceCounted(true);
430 * Grabs a wake lock and sends intent as an ordered broadcast.
431 * The resultReceiver will check for errors and ACK/NACK back
434 * @param intent intent to broadcast
435 * @param permission Receivers are required to have this permission
437 void dispatch(Intent intent, String permission) {
438 // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
439 // receivers time to take their own wake locks.
440 mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
441 mContext.sendOrderedBroadcast(intent, permission, mResultReceiver,
442 this, Activity.RESULT_OK, null, null);
446 * Called when SIM_FULL message is received from the RIL. Notifies interested
447 * parties that SIM storage for SMS messages is full.
449 private void handleIccFull(){
450 // broadcast SIM_FULL intent
451 Intent intent = new Intent(Intents.SIM_FULL_ACTION);
452 mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
453 mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
457 * Called when a status report is received. This should correspond to
458 * a previously successful SEND.
460 * @param ar AsyncResult passed into the message handler. ar.result should
461 * be a String representing the status report PDU, as ASCII hex.
463 protected abstract void handleStatusReport(AsyncResult ar);
466 * Called when SMS send completes. Broadcasts a sentIntent on success.
467 * On failure, either sets up retries or broadcasts a sentIntent with
468 * the failure in the result code.
470 * @param ar AsyncResult passed into the message handler. ar.result should
471 * an SmsResponse instance if send was successful. ar.userObj
472 * should be an SmsTracker instance.
474 protected void handleSendComplete(AsyncResult ar) {
475 SmsTracker tracker = (SmsTracker) ar.userObj;
476 PendingIntent sentIntent = tracker.mSentIntent;
478 if (ar.exception == null) {
480 Log.d(TAG, "SMS send complete. Broadcasting "
481 + "intent: " + sentIntent);
484 if (tracker.mDeliveryIntent != null) {
485 // Expecting a status report. Add it to the list.
486 int messageRef = ((SmsResponse)ar.result).messageRef;
487 tracker.mMessageRef = messageRef;
488 deliveryPendingList.add(tracker);
491 if (sentIntent != null) {
493 if (mRemainingMessages > -1) {
494 mRemainingMessages--;
497 if (mRemainingMessages == 0) {
498 Intent sendNext = new Intent();
499 sendNext.putExtra(SEND_NEXT_MSG_EXTRA, true);
500 sentIntent.send(mContext, Activity.RESULT_OK, sendNext);
502 sentIntent.send(Activity.RESULT_OK);
504 } catch (CanceledException ex) {}
508 Log.d(TAG, "SMS send failed");
511 int ss = mPhone.getServiceState().getState();
513 if (ss != ServiceState.STATE_IN_SERVICE) {
514 handleNotInService(ss, tracker);
515 } else if ((((CommandException)(ar.exception)).getCommandError()
516 == CommandException.Error.SMS_FAIL_RETRY) &&
517 tracker.mRetryCount < MAX_SEND_RETRIES) {
518 // Retry after a delay if needed.
519 // TODO: According to TS 23.040, 9.2.3.6, we should resend
520 // with the same TP-MR as the failed message, and
521 // TP-RD set to 1. However, we don't have a means of
522 // knowing the MR for the failed message (EF_SMSstatus
523 // may or may not have the MR corresponding to this
524 // message, depending on the failure). Also, in some
525 // implementations this retry is handled by the baseband.
526 tracker.mRetryCount++;
527 Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
528 sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
529 } else if (tracker.mSentIntent != null) {
530 int error = RESULT_ERROR_GENERIC_FAILURE;
532 if (((CommandException)(ar.exception)).getCommandError()
533 == CommandException.Error.FDN_CHECK_FAILURE) {
534 error = RESULT_ERROR_FDN_CHECK_FAILURE;
536 // Done retrying; return an error to the app.
538 Intent fillIn = new Intent();
539 if (ar.result != null) {
540 fillIn.putExtra("errorCode", ((SmsResponse)ar.result).errorCode);
542 if (mRemainingMessages > -1) {
543 mRemainingMessages--;
546 if (mRemainingMessages == 0) {
547 fillIn.putExtra(SEND_NEXT_MSG_EXTRA, true);
550 tracker.mSentIntent.send(mContext, error, fillIn);
551 } catch (CanceledException ex) {}
557 * Handles outbound message when the phone is not in service.
559 * @param ss Current service state. Valid values are:
563 * @param tracker An SmsTracker for the current message.
565 protected void handleNotInService(int ss, SmsTracker tracker) {
566 if (tracker.mSentIntent != null) {
568 if (ss == ServiceState.STATE_POWER_OFF) {
569 tracker.mSentIntent.send(RESULT_ERROR_RADIO_OFF);
571 tracker.mSentIntent.send(RESULT_ERROR_NO_SERVICE);
573 } catch (CanceledException ex) {}
578 * Dispatches an incoming SMS messages.
580 * @param sms the incoming message from the phone
581 * @return a result code from {@link Telephony.Sms.Intents}, or
582 * {@link Activity#RESULT_OK} if the message has been broadcast
585 public abstract int dispatchMessage(SmsMessageBase sms);
589 * If this is the last part send the parts out to the application, otherwise
590 * the part is stored for later processing.
592 * NOTE: concatRef (naturally) needs to be non-null, but portAddrs can be null.
593 * @return a result code from {@link Telephony.Sms.Intents}, or
594 * {@link Activity#RESULT_OK} if the message has been broadcast
597 protected int processMessagePart(SmsMessageBase sms,
598 SmsHeader.ConcatRef concatRef, SmsHeader.PortAddrs portAddrs) {
600 // Lookup all other related parts
601 StringBuilder where = new StringBuilder("reference_number =");
602 where.append(concatRef.refNumber);
603 where.append(" AND address = ?");
604 String[] whereArgs = new String[] {sms.getOriginatingAddress()};
606 byte[][] pdus = null;
607 Cursor cursor = null;
609 cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
610 int cursorCount = cursor.getCount();
611 if (cursorCount != concatRef.msgCount - 1) {
612 // We don't have all the parts yet, store this one away
613 ContentValues values = new ContentValues();
614 values.put("date", new Long(sms.getTimestampMillis()));
615 values.put("pdu", HexDump.toHexString(sms.getPdu()));
616 values.put("address", sms.getOriginatingAddress());
617 values.put("reference_number", concatRef.refNumber);
618 values.put("count", concatRef.msgCount);
619 values.put("sequence", concatRef.seqNumber);
620 if (portAddrs != null) {
621 values.put("destination_port", portAddrs.destPort);
623 mResolver.insert(mRawUri, values);
624 return Intents.RESULT_SMS_HANDLED;
627 // All the parts are in place, deal with them
628 int pduColumn = cursor.getColumnIndex("pdu");
629 int sequenceColumn = cursor.getColumnIndex("sequence");
631 pdus = new byte[concatRef.msgCount][];
632 for (int i = 0; i < cursorCount; i++) {
634 int cursorSequence = (int)cursor.getLong(sequenceColumn);
635 pdus[cursorSequence - 1] = HexDump.hexStringToByteArray(
636 cursor.getString(pduColumn));
638 // This one isn't in the DB, so add it
639 pdus[concatRef.seqNumber - 1] = sms.getPdu();
641 // Remove the parts from the database
642 mResolver.delete(mRawUri, where.toString(), whereArgs);
643 } catch (SQLException e) {
644 Log.e(TAG, "Can't access multipart SMS database", e);
645 // TODO: Would OUT_OF_MEMORY be more appropriate?
646 return Intents.RESULT_SMS_GENERIC_ERROR;
648 if (cursor != null) cursor.close();
652 * TODO(cleanup): The following code has duplicated logic with
653 * the radio-specific dispatchMessage code, which is fragile,
654 * in addition to being redundant. Instead, if this method
655 * maybe returned the reassembled message (or just contents),
656 * the following code (which is not really related to
657 * reconstruction) could be better consolidated.
660 // Dispatch the PDUs to applications
661 if (portAddrs != null) {
662 if (portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
663 // Build up the data stream
664 ByteArrayOutputStream output = new ByteArrayOutputStream();
665 for (int i = 0; i < concatRef.msgCount; i++) {
666 SmsMessage msg = SmsMessage.createFromPdu(pdus[i]);
667 byte[] data = msg.getUserData();
668 output.write(data, 0, data.length);
671 return mWapPush.dispatchWapPdu(output.toByteArray());
673 // The messages were sent to a port, so concoct a URI for it
674 dispatchPortAddressedPdus(pdus, portAddrs.destPort);
677 // The messages were not sent to a port
680 return Activity.RESULT_OK;
684 * Dispatches standard PDUs to interested applications
686 * @param pdus The raw PDUs making up the message
688 protected void dispatchPdus(byte[][] pdus) {
689 Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
690 intent.putExtra("pdus", pdus);
691 dispatch(intent, "android.permission.RECEIVE_SMS");
695 * Dispatches port addressed PDUs to interested applications
697 * @param pdus The raw PDUs making up the message
698 * @param port The destination port of the messages
700 protected void dispatchPortAddressedPdus(byte[][] pdus, int port) {
701 Uri uri = Uri.parse("sms://localhost:" + port);
702 Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
703 intent.putExtra("pdus", pdus);
704 dispatch(intent, "android.permission.RECEIVE_SMS");
708 * Send a data based SMS to a specific application port.
710 * @param destAddr the address to send the message to
711 * @param scAddr is the service center address or null to use
712 * the current default SMSC
713 * @param destPort the port to deliver the message to
714 * @param data the body of the message to send
715 * @param sentIntent if not NULL this <code>PendingIntent</code> is
716 * broadcast when the message is successfully sent, or failed.
717 * The result code will be <code>Activity.RESULT_OK<code> for success,
718 * or one of these errors:<br>
719 * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
720 * <code>RESULT_ERROR_RADIO_OFF</code><br>
721 * <code>RESULT_ERROR_NULL_PDU</code><br>
722 * <code>RESULT_ERROR_NO_SERVICE</code><br>.
723 * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
724 * the extra "errorCode" containing a radio technology specific value,
725 * generally only useful for troubleshooting.<br>
726 * The per-application based SMS control checks sentIntent. If sentIntent
727 * is NULL the caller will be checked against all unknown applications,
728 * which cause smaller number of SMS to be sent in checking period.
729 * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
730 * broadcast when the message is delivered to the recipient. The
731 * raw pdu of the status report is in the extended data ("pdu").
733 protected abstract void sendData(String destAddr, String scAddr, int destPort,
734 byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent);
737 * Send a text based SMS.
739 * @param destAddr the address to send the message to
740 * @param scAddr is the service center address or null to use
741 * the current default SMSC
742 * @param text the body of the message to send
743 * @param sentIntent if not NULL this <code>PendingIntent</code> is
744 * broadcast when the message is successfully sent, or failed.
745 * The result code will be <code>Activity.RESULT_OK<code> for success,
746 * or one of these errors:<br>
747 * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
748 * <code>RESULT_ERROR_RADIO_OFF</code><br>
749 * <code>RESULT_ERROR_NULL_PDU</code><br>
750 * <code>RESULT_ERROR_NO_SERVICE</code><br>.
751 * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
752 * the extra "errorCode" containing a radio technology specific value,
753 * generally only useful for troubleshooting.<br>
754 * The per-application based SMS control checks sentIntent. If sentIntent
755 * is NULL the caller will be checked against all unknown applications,
756 * which cause smaller number of SMS to be sent in checking period.
757 * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
758 * broadcast when the message is delivered to the recipient. The
759 * raw pdu of the status report is in the extended data ("pdu").
761 protected abstract void sendText(String destAddr, String scAddr,
762 String text, PendingIntent sentIntent, PendingIntent deliveryIntent);
765 * Send a multi-part text based SMS.
767 * @param destAddr the address to send the message to
768 * @param scAddr is the service center address or null to use
769 * the current default SMSC
770 * @param parts an <code>ArrayList</code> of strings that, in order,
771 * comprise the original message
772 * @param sentIntents if not null, an <code>ArrayList</code> of
773 * <code>PendingIntent</code>s (one for each message part) that is
774 * broadcast when the corresponding message part has been sent.
775 * The result code will be <code>Activity.RESULT_OK<code> for success,
776 * or one of these errors:
777 * <code>RESULT_ERROR_GENERIC_FAILURE</code>
778 * <code>RESULT_ERROR_RADIO_OFF</code>
779 * <code>RESULT_ERROR_NULL_PDU</code>
780 * <code>RESULT_ERROR_NO_SERVICE</code>.
781 * The per-application based SMS control checks sentIntent. If sentIntent
782 * is NULL the caller will be checked against all unknown applications,
783 * which cause smaller number of SMS to be sent in checking period.
784 * @param deliveryIntents if not null, an <code>ArrayList</code> of
785 * <code>PendingIntent</code>s (one for each message part) that is
786 * broadcast when the corresponding message part has been delivered
787 * to the recipient. The raw pdu of the status report is in the
788 * extended data ("pdu").
790 protected abstract void sendMultipartText(String destAddr, String scAddr,
791 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
792 ArrayList<PendingIntent> deliveryIntents);
797 * @param smsc the SMSC to send the message through, or NULL for the
799 * @param pdu the raw PDU to send
800 * @param sentIntent if not NULL this <code>Intent</code> is
801 * broadcast when the message is successfully sent, or failed.
802 * The result code will be <code>Activity.RESULT_OK<code> for success,
803 * or one of these errors:
804 * <code>RESULT_ERROR_GENERIC_FAILURE</code>
805 * <code>RESULT_ERROR_RADIO_OFF</code>
806 * <code>RESULT_ERROR_NULL_PDU</code>
807 * <code>RESULT_ERROR_NO_SERVICE</code>.
808 * The per-application based SMS control checks sentIntent. If sentIntent
809 * is NULL the caller will be checked against all unknown applications,
810 * which cause smaller number of SMS to be sent in checking period.
811 * @param deliveryIntent if not NULL this <code>Intent</code> is
812 * broadcast when the message is delivered to the recipient. The
813 * raw pdu of the status report is in the extended data ("pdu").
815 protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
816 PendingIntent deliveryIntent) {
817 if (mSmsSendDisabled) {
818 if (sentIntent != null) {
820 sentIntent.send(RESULT_ERROR_NO_SERVICE);
821 } catch (CanceledException ex) {}
823 Log.d(TAG, "Device does not support sending sms.");
828 if (sentIntent != null) {
830 sentIntent.send(RESULT_ERROR_NULL_PDU);
831 } catch (CanceledException ex) {}
836 HashMap<String, Object> map = new HashMap<String, Object>();
837 map.put("smsc", smsc);
840 SmsTracker tracker = new SmsTracker(map, sentIntent,
842 int ss = mPhone.getServiceState().getState();
844 if (ss != ServiceState.STATE_IN_SERVICE) {
845 handleNotInService(ss, tracker);
847 String appName = getAppNameByIntent(sentIntent);
848 if (mCounter.check(appName, SINGLE_PART_SMS)) {
851 sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
857 * Post an alert while SMS needs user confirm.
859 * An SmsTracker for the current message.
861 protected void handleReachSentLimit(SmsTracker tracker) {
862 if (mSTrackers.size() >= MO_MSG_QUEUE_LIMIT) {
863 // Deny the sending when the queue limit is reached.
865 tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
866 } catch (CanceledException ex) {
867 Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
872 Resources r = Resources.getSystem();
874 String appName = getAppNameByIntent(tracker.mSentIntent);
876 AlertDialog d = new AlertDialog.Builder(mContext)
877 .setTitle(r.getString(R.string.sms_control_title))
878 .setMessage(appName + " " + r.getString(R.string.sms_control_message))
879 .setPositiveButton(r.getString(R.string.sms_control_yes), mListener)
880 .setNegativeButton(r.getString(R.string.sms_control_no), mListener)
883 d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
886 mSTrackers.add(tracker);
887 sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d),
888 DEFAULT_SMS_TIMEOUT);
891 protected String getAppNameByIntent(PendingIntent intent) {
892 Resources r = Resources.getSystem();
893 return (intent != null) ? intent.getTargetPackage()
894 : r.getString(R.string.sms_control_default_app_name);
898 * Send the message along to the radio.
900 * @param tracker holds the SMS message to send
902 protected abstract void sendSms(SmsTracker tracker);
905 * Send the multi-part SMS based on multipart Sms tracker
907 * @param tracker holds the multipart Sms tracker ready to be sent
909 protected abstract void sendMultipartSms (SmsTracker tracker);
912 * Activate or deactivate cell broadcast SMS.
915 * 0 = activate, 1 = deactivate
917 * Callback message is empty on completion
919 public abstract void activateCellBroadcastSms(int activate, Message response);
922 * Query the current configuration of cell broadcast SMS.
925 * Callback message contains the configuration from the modem on completion
926 * @see #setCellBroadcastConfig
928 public abstract void getCellBroadcastSmsConfig(Message response);
931 * Configure cell broadcast SMS.
933 * @param configValuesArray
934 * The first element defines the number of triples that follow.
935 * A triple is made up of the service category, the language identifier
936 * and a boolean that specifies whether the category is set active.
938 * Callback message is empty on completion
940 public abstract void setCellBroadcastConfig(int[] configValuesArray, Message response);
943 * Send an acknowledge message.
944 * @param success indicates that last message was successfully received.
945 * @param result result code indicating any error
946 * @param response callback message sent when operation completes.
948 protected abstract void acknowledgeLastIncomingSms(boolean success,
949 int result, Message response);
952 * Notify interested apps if the framework has rejected an incoming SMS,
953 * and send an acknowledge message to the network.
954 * @param success indicates that last message was successfully received.
955 * @param result result code indicating any error
956 * @param response callback message sent when operation completes.
958 private void notifyAndAcknowledgeLastIncomingSms(boolean success,
959 int result, Message response) {
961 // broadcast SMS_REJECTED_ACTION intent
962 Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
963 intent.putExtra("result", result);
964 mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
965 mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
967 acknowledgeLastIncomingSms(success, result, response);
971 * Check if a SmsTracker holds multi-part Sms
973 * @param tracker a SmsTracker could hold a multi-part Sms
974 * @return true for tracker holds Multi-parts Sms
976 private boolean isMultipartTracker (SmsTracker tracker) {
977 HashMap map = tracker.mData;
978 return ( map.get("parts") != null);
982 * Keeps track of an SMS that has been sent to the RIL, until it has
983 * successfully been sent, or we're done trying.
986 static protected class SmsTracker {
987 // fields need to be public for derived SmsDispatchers
988 public HashMap<String, Object> mData;
989 public int mRetryCount;
990 public int mMessageRef;
992 public PendingIntent mSentIntent;
993 public PendingIntent mDeliveryIntent;
995 SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
996 PendingIntent deliveryIntent) {
998 mSentIntent = sentIntent;
999 mDeliveryIntent = deliveryIntent;
1004 protected SmsTracker SmsTrackerFactory(HashMap<String, Object> data, PendingIntent sentIntent,
1005 PendingIntent deliveryIntent) {
1006 return new SmsTracker(data, sentIntent, deliveryIntent);
1009 public void initSipStack(boolean isObg) {
1010 // This function should be overridden by the classes that support
1011 // switching modes such as the CdmaSMSDispatcher.
1012 // Not implemented in GsmSMSDispatcher.
1013 Log.e(TAG, "Error! This function should never be executed.");
1016 public void switchToCdma() {
1017 // This function should be overridden by the classes that support
1018 // switching modes such as the CdmaSMSDispatcher.
1019 // Not implemented in GsmSMSDispatcher.
1020 Log.e(TAG, "Error! This function should never be executed.");
1023 public void switchToGsm() {
1024 // This function should be overridden by the classes that support
1025 // switching modes such as the CdmaSMSDispatcher.
1026 // Not implemented in GsmSMSDispatcher.
1027 Log.e(TAG, "Error! This function should never be executed.");
1030 private DialogInterface.OnClickListener mListener =
1031 new DialogInterface.OnClickListener() {
1033 public void onClick(DialogInterface dialog, int which) {
1034 if (which == DialogInterface.BUTTON_POSITIVE) {
1035 Log.d(TAG, "click YES to send out sms");
1036 sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS));
1037 } else if (which == DialogInterface.BUTTON_NEGATIVE) {
1038 Log.d(TAG, "click NO to stop sending");
1039 sendMessage(obtainMessage(EVENT_STOP_SENDING));
1044 private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
1046 public void onReceive(Context context, Intent intent) {
1047 if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_FULL)) {
1048 mStorageAvailable = false;
1049 mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
1050 } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)) {
1051 mStorageAvailable = true;
1052 mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
1054 // Assume the intent is one of the SMS receive intents that
1055 // was sent as an ordered broadcast. Check result and ACK.
1056 int rc = getResultCode();
1057 boolean success = (rc == Activity.RESULT_OK)
1058 || (rc == Intents.RESULT_SMS_HANDLED);
1060 // For a multi-part message, this only ACKs the last part.
1061 // Previous parts were ACK'd as they were received.
1062 acknowledgeLastIncomingSms(success, rc, null);
1067 protected abstract void handleBroadcastSms(AsyncResult ar);
1069 protected void dispatchBroadcastPdus(byte[][] pdus) {
1070 Intent intent = new Intent("android.provider.telephony.SMS_CB_RECEIVED");
1071 intent.putExtra("pdus", pdus);
1074 Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus");
1076 dispatch(intent, "android.permission.RECEIVE_SMS");