OSDN Git Service

am 0fbe1dfb: Merge "cherrypick from master: Change-Id: I169749dc594ca1d79a802db4c53ec...
[android-x86/frameworks-base.git] / telephony / java / com / android / internal / telephony / SMSDispatcher.java
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.internal.telephony;
18
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;
49
50 import com.android.internal.util.HexDump;
51
52 import java.io.ByteArrayOutputStream;
53 import java.util.ArrayList;
54 import java.util.HashMap;
55 import java.util.Random;
56
57 import com.android.internal.R;
58
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;
65
66
67 public abstract class SMSDispatcher extends Handler {
68     private static final String TAG = "SMS";
69     private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg";
70
71     /** Default checking period for SMS sent without user permit */
72     private static final int DEFAULT_SMS_CHECK_PERIOD = 3600000;
73
74     /** Default number of SMS sent in checking period without user permit */
75     private static final int DEFAULT_SMS_MAX_COUNT = 100;
76
77     /** Default timeout for SMS sent query */
78     private static final int DEFAULT_SMS_TIMEOUT = 6000;
79
80     protected static final String[] RAW_PROJECTION = new String[] {
81         "pdu",
82         "sequence",
83         "destination_port",
84     };
85
86     static final protected int EVENT_NEW_SMS = 1;
87
88     static final protected int EVENT_SEND_SMS_COMPLETE = 2;
89
90     /** Retry sending a previously failed SMS message */
91     static final protected int EVENT_SEND_RETRY = 3;
92
93     /** Status report received */
94     static final protected int EVENT_NEW_SMS_STATUS_REPORT = 5;
95
96     /** SIM/RUIM storage is full */
97     static final protected int EVENT_ICC_FULL = 6;
98
99     /** SMS confirm required */
100     static final protected int EVENT_POST_ALERT = 7;
101
102     /** Send the user confirmed SMS */
103     static final protected int EVENT_SEND_CONFIRMED_SMS = 8;
104
105     /** Alert is timeout */
106     static final protected int EVENT_ALERT_TIMEOUT = 9;
107
108     /** Stop the sending */
109     static final protected int EVENT_STOP_SENDING = 10;
110
111     /** Memory status reporting is acknowledged by RIL */
112     static final protected int EVENT_REPORT_MEMORY_STATUS_DONE = 11;
113
114     /** Radio is ON */
115     static final protected int EVENT_RADIO_ON = 12;
116
117     /** New broadcast SMS */
118     static final protected int EVENT_NEW_BROADCAST_SMS = 13;
119
120     protected Phone mPhone;
121     protected Context mContext;
122     protected ContentResolver mResolver;
123     protected CommandsInterface mCm;
124
125     protected final WapPushOverSms mWapPush;
126
127     protected final Uri mRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
128
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;
137
138     /**
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.
142      */
143     private static int sConcatenatedRef;
144
145     private SmsCounter mCounter;
146
147     private ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT);
148
149     /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
150     private PowerManager.WakeLock mWakeLock;
151
152     /**
153      * Hold the wake lock for 5 seconds, which should be enough time for
154      * any receiver(s) to grab its own wake lock.
155      */
156     private final int WAKE_LOCK_TIMEOUT = 5000;
157
158     protected boolean mStorageAvailable = true;
159     protected boolean mReportMemoryStatusPending = false;
160
161     /* Flags indicating whether the current device allows sms service */
162     protected boolean mSmsCapable = true;
163     protected boolean mSmsReceiveDisabled;
164     protected boolean mSmsSendDisabled;
165
166     protected static int mRemainingMessages = -1;
167
168     protected static int getNextConcatenatedRef() {
169         sConcatenatedRef += 1;
170         return sConcatenatedRef;
171     }
172
173     /**
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
176      *  period.
177      */
178     private class SmsCounter {
179         private int mCheckPeriod;
180         private int mMaxAllowed;
181         private HashMap<String, ArrayList<Long>> mSmsStamp;
182
183         /**
184          * Create SmsCounter
185          * @param mMax is the number of SMS allowed without user permit
186          * @param mPeriod is the checking period
187          */
188         SmsCounter(int mMax, int mPeriod) {
189             mMaxAllowed = mMax;
190             mCheckPeriod = mPeriod;
191             mSmsStamp = new HashMap<String, ArrayList<Long>> ();
192         }
193
194         /**
195          * Check to see if an application allow to send new SMS messages
196          *
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
201          */
202         boolean check(String appName, int smsWaiting) {
203             if (!mSmsStamp.containsKey(appName)) {
204                 mSmsStamp.put(appName, new ArrayList<Long>());
205             }
206
207             return isUnderLimit(mSmsStamp.get(appName), smsWaiting);
208         }
209
210         private boolean isUnderLimit(ArrayList<Long> sent, int smsWaiting) {
211             Long ct =  System.currentTimeMillis();
212
213             Log.d(TAG, "SMS send size=" + sent.size() + "time=" + ct);
214
215             while (sent.size() > 0 && (ct - sent.get(0)) > mCheckPeriod ) {
216                     sent.remove(0);
217             }
218
219
220             if ( (sent.size() + smsWaiting) <= mMaxAllowed) {
221                 for (int i = 0; i < smsWaiting; i++ ) {
222                     sent.add(ct);
223                 }
224                 return true;
225             }
226             return false;
227         }
228     }
229
230     protected SMSDispatcher(PhoneBase phone) {
231         mPhone = phone;
232         mWapPush = new WapPushOverSms(phone, this);
233         mContext = phone.getContext();
234         mResolver = mContext.getContentResolver();
235         mCm = phone.mCM;
236
237         createWakelock();
238
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);
246
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);
251
252         // Don't always start message ref at 0.
253         sConcatenatedRef = new Random().nextInt(256);
254
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);
261
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);
271     }
272
273     public void dispose() {
274         mCm.unSetOnNewSMS(this);
275         mCm.unSetOnSmsStatus(this);
276         mCm.unSetOnIccSmsFull(this);
277         mCm.unregisterForOn(this);
278     }
279
280     @Override
281     protected void finalize() {
282         Log.d(TAG, "SMSDispatcher finalized");
283     }
284
285
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
289      *       will be dropped.
290      */
291     /** Sent messages awaiting a delivery status report. */
292     protected final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>();
293
294     /**
295      * Handles events coming from the phone stack. Overridden from handler.
296      *
297      * @param msg the message to handle
298      */
299     @Override
300     public void handleMessage(Message msg) {
301         AsyncResult ar;
302
303         switch (msg.what) {
304         case EVENT_NEW_SMS:
305             // A new SMS has been received by the device
306             if (Config.LOGD) {
307                 Log.d(TAG, "New SMS Message Received");
308             }
309
310             SmsMessage sms;
311
312             ar = (AsyncResult) msg.obj;
313
314             if (ar.exception != null) {
315                 Log.e(TAG, "Exception processing incoming SMS. Exception:" + ar.exception);
316                 return;
317             }
318
319             sms = (SmsMessage) ar.result;
320             try {
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);
327                 }
328             } catch (RuntimeException ex) {
329                 Log.e(TAG, "Exception dispatching message", ex);
330                 notifyAndAcknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null);
331             }
332
333             break;
334
335         case EVENT_SEND_SMS_COMPLETE:
336             // An outbound SMS has been successfully transferred, or failed.
337             handleSendComplete((AsyncResult) msg.obj);
338             break;
339
340         case EVENT_SEND_RETRY:
341             sendSms((SmsTracker) msg.obj);
342             break;
343
344         case EVENT_NEW_SMS_STATUS_REPORT:
345             handleStatusReport((AsyncResult)msg.obj);
346             break;
347
348         case EVENT_ICC_FULL:
349             handleIccFull();
350             break;
351
352         case EVENT_POST_ALERT:
353             handleReachSentLimit((SmsTracker)(msg.obj));
354             break;
355
356         case EVENT_ALERT_TIMEOUT:
357             ((AlertDialog)(msg.obj)).dismiss();
358             msg.obj = null;
359             if (mSTrackers.isEmpty() == false) {
360                 try {
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");
365                 }
366             }
367             if (Config.LOGD) {
368                 Log.d(TAG, "EVENT_ALERT_TIMEOUT, message stop sending");
369             }
370             break;
371
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);
377                 } else {
378                     sendSms(sTracker);
379                 }
380                 removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
381             }
382             break;
383
384         case EVENT_STOP_SENDING:
385             if (mSTrackers.isEmpty() == false) {
386                 // Remove the latest one.
387                 try {
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");
392                 }
393                 removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
394             }
395             break;
396
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);
403             } else {
404                 mReportMemoryStatusPending = false;
405             }
406             break;
407
408         case EVENT_RADIO_ON:
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));
414             }
415             break;
416
417         case EVENT_NEW_BROADCAST_SMS:
418             handleBroadcastSms((AsyncResult)msg.obj);
419             break;
420         }
421     }
422
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);
427     }
428
429     /**
430      * Grabs a wake lock and sends intent as an ordered broadcast.
431      * The resultReceiver will check for errors and ACK/NACK back
432      * to the RIL.
433      *
434      * @param intent intent to broadcast
435      * @param permission Receivers are required to have this permission
436      */
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);
443     }
444
445     /**
446      * Called when SIM_FULL message is received from the RIL.  Notifies interested
447      * parties that SIM storage for SMS messages is full.
448      */
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");
454     }
455
456     /**
457      * Called when a status report is received.  This should correspond to
458      * a previously successful SEND.
459      *
460      * @param ar AsyncResult passed into the message handler.  ar.result should
461      *           be a String representing the status report PDU, as ASCII hex.
462      */
463     protected abstract void handleStatusReport(AsyncResult ar);
464
465     /**
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.
469      *
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.
473      */
474     protected void handleSendComplete(AsyncResult ar) {
475         SmsTracker tracker = (SmsTracker) ar.userObj;
476         PendingIntent sentIntent = tracker.mSentIntent;
477
478         if (ar.exception == null) {
479             if (Config.LOGD) {
480                 Log.d(TAG, "SMS send complete. Broadcasting "
481                         + "intent: " + sentIntent);
482             }
483
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);
489             }
490
491             if (sentIntent != null) {
492                 try {
493                     if (mRemainingMessages > -1) {
494                         mRemainingMessages--;
495                     }
496
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);
501                     } else {
502                         sentIntent.send(Activity.RESULT_OK);
503                     }
504                 } catch (CanceledException ex) {}
505             }
506         } else {
507             if (Config.LOGD) {
508                 Log.d(TAG, "SMS send failed");
509             }
510
511             int ss = mPhone.getServiceState().getState();
512
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;
531
532                 if (((CommandException)(ar.exception)).getCommandError()
533                         == CommandException.Error.FDN_CHECK_FAILURE) {
534                     error = RESULT_ERROR_FDN_CHECK_FAILURE;
535                 }
536                 // Done retrying; return an error to the app.
537                 try {
538                     Intent fillIn = new Intent();
539                     if (ar.result != null) {
540                         fillIn.putExtra("errorCode", ((SmsResponse)ar.result).errorCode);
541                     }
542                     if (mRemainingMessages > -1) {
543                         mRemainingMessages--;
544                     }
545
546                     if (mRemainingMessages == 0) {
547                         fillIn.putExtra(SEND_NEXT_MSG_EXTRA, true);
548                     }
549
550                     tracker.mSentIntent.send(mContext, error, fillIn);
551                 } catch (CanceledException ex) {}
552             }
553         }
554     }
555
556     /**
557      * Handles outbound message when the phone is not in service.
558      *
559      * @param ss     Current service state.  Valid values are:
560      *                  OUT_OF_SERVICE
561      *                  EMERGENCY_ONLY
562      *                  POWER_OFF
563      * @param tracker   An SmsTracker for the current message.
564      */
565     protected void handleNotInService(int ss, SmsTracker tracker) {
566         if (tracker.mSentIntent != null) {
567             try {
568                 if (ss == ServiceState.STATE_POWER_OFF) {
569                     tracker.mSentIntent.send(RESULT_ERROR_RADIO_OFF);
570                 } else {
571                     tracker.mSentIntent.send(RESULT_ERROR_NO_SERVICE);
572                 }
573             } catch (CanceledException ex) {}
574         }
575     }
576
577     /**
578      * Dispatches an incoming SMS messages.
579      *
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
583      *         to applications
584      */
585     public abstract int dispatchMessage(SmsMessageBase sms);
586
587
588     /**
589      * If this is the last part send the parts out to the application, otherwise
590      * the part is stored for later processing.
591      *
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
595      *         to applications
596      */
597     protected int processMessagePart(SmsMessageBase sms,
598             SmsHeader.ConcatRef concatRef, SmsHeader.PortAddrs portAddrs) {
599
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()};
605
606         byte[][] pdus = null;
607         Cursor cursor = null;
608         try {
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);
622                 }
623                 mResolver.insert(mRawUri, values);
624                 return Intents.RESULT_SMS_HANDLED;
625             }
626
627             // All the parts are in place, deal with them
628             int pduColumn = cursor.getColumnIndex("pdu");
629             int sequenceColumn = cursor.getColumnIndex("sequence");
630
631             pdus = new byte[concatRef.msgCount][];
632             for (int i = 0; i < cursorCount; i++) {
633                 cursor.moveToNext();
634                 int cursorSequence = (int)cursor.getLong(sequenceColumn);
635                 pdus[cursorSequence - 1] = HexDump.hexStringToByteArray(
636                         cursor.getString(pduColumn));
637             }
638             // This one isn't in the DB, so add it
639             pdus[concatRef.seqNumber - 1] = sms.getPdu();
640
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;
647         } finally {
648             if (cursor != null) cursor.close();
649         }
650
651         /**
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.
658          */
659
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);
669                 }
670                 // Handle the PUSH
671                 return mWapPush.dispatchWapPdu(output.toByteArray());
672             } else {
673                 // The messages were sent to a port, so concoct a URI for it
674                 dispatchPortAddressedPdus(pdus, portAddrs.destPort);
675             }
676         } else {
677             // The messages were not sent to a port
678             dispatchPdus(pdus);
679         }
680         return Activity.RESULT_OK;
681     }
682
683     /**
684      * Dispatches standard PDUs to interested applications
685      *
686      * @param pdus The raw PDUs making up the message
687      */
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");
692     }
693
694     /**
695      * Dispatches port addressed PDUs to interested applications
696      *
697      * @param pdus The raw PDUs making up the message
698      * @param port The destination port of the messages
699      */
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");
705     }
706
707     /**
708      * Send a data based SMS to a specific application port.
709      *
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").
732      */
733     protected abstract void sendData(String destAddr, String scAddr, int destPort,
734             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent);
735
736     /**
737      * Send a text based SMS.
738      *
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").
760      */
761     protected abstract void sendText(String destAddr, String scAddr,
762             String text, PendingIntent sentIntent, PendingIntent deliveryIntent);
763
764     /**
765      * Send a multi-part text based SMS.
766      *
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").
789      */
790     protected abstract void sendMultipartText(String destAddr, String scAddr,
791             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
792             ArrayList<PendingIntent> deliveryIntents);
793
794     /**
795      * Send a SMS
796      *
797      * @param smsc the SMSC to send the message through, or NULL for the
798      *  default SMSC
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").
814      */
815     protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
816             PendingIntent deliveryIntent) {
817         if (mSmsSendDisabled) {
818             if (sentIntent != null) {
819                 try {
820                     sentIntent.send(RESULT_ERROR_NO_SERVICE);
821                 } catch (CanceledException ex) {}
822             }
823             Log.d(TAG, "Device does not support sending sms.");
824             return;
825         }
826
827         if (pdu == null) {
828             if (sentIntent != null) {
829                 try {
830                     sentIntent.send(RESULT_ERROR_NULL_PDU);
831                 } catch (CanceledException ex) {}
832             }
833             return;
834         }
835
836         HashMap<String, Object> map = new HashMap<String, Object>();
837         map.put("smsc", smsc);
838         map.put("pdu", pdu);
839
840         SmsTracker tracker = new SmsTracker(map, sentIntent,
841                 deliveryIntent);
842         int ss = mPhone.getServiceState().getState();
843
844         if (ss != ServiceState.STATE_IN_SERVICE) {
845             handleNotInService(ss, tracker);
846         } else {
847             String appName = getAppNameByIntent(sentIntent);
848             if (mCounter.check(appName, SINGLE_PART_SMS)) {
849                 sendSms(tracker);
850             } else {
851                 sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
852             }
853         }
854     }
855
856     /**
857      * Post an alert while SMS needs user confirm.
858      *
859      * An SmsTracker for the current message.
860      */
861     protected void handleReachSentLimit(SmsTracker tracker) {
862         if (mSTrackers.size() >= MO_MSG_QUEUE_LIMIT) {
863             // Deny the sending when the queue limit is reached.
864             try {
865                 tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
866             } catch (CanceledException ex) {
867                 Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
868             }
869             return;
870         }
871
872         Resources r = Resources.getSystem();
873
874         String appName = getAppNameByIntent(tracker.mSentIntent);
875
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)
881                 .create();
882
883         d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
884         d.show();
885
886         mSTrackers.add(tracker);
887         sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d),
888                 DEFAULT_SMS_TIMEOUT);
889     }
890
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);
895     }
896
897     /**
898      * Send the message along to the radio.
899      *
900      * @param tracker holds the SMS message to send
901      */
902     protected abstract void sendSms(SmsTracker tracker);
903
904     /**
905      * Send the multi-part SMS based on multipart Sms tracker
906      *
907      * @param tracker holds the multipart Sms tracker ready to be sent
908      */
909     protected abstract void sendMultipartSms (SmsTracker tracker);
910
911     /**
912      * Activate or deactivate cell broadcast SMS.
913      *
914      * @param activate
915      *            0 = activate, 1 = deactivate
916      * @param response
917      *            Callback message is empty on completion
918      */
919     public abstract void activateCellBroadcastSms(int activate, Message response);
920
921     /**
922      * Query the current configuration of cell broadcast SMS.
923      *
924      * @param response
925      *            Callback message contains the configuration from the modem on completion
926      *            @see #setCellBroadcastConfig
927      */
928     public abstract void getCellBroadcastSmsConfig(Message response);
929
930     /**
931      * Configure cell broadcast SMS.
932      *
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.
937      * @param response
938      *            Callback message is empty on completion
939      */
940     public abstract void setCellBroadcastConfig(int[] configValuesArray, Message response);
941
942     /**
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.
947      */
948     protected abstract void acknowledgeLastIncomingSms(boolean success,
949             int result, Message response);
950
951     /**
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.
957      */
958     private void notifyAndAcknowledgeLastIncomingSms(boolean success,
959             int result, Message response) {
960         if (!success) {
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");
966         }
967         acknowledgeLastIncomingSms(success, result, response);
968     }
969
970     /**
971      * Check if a SmsTracker holds multi-part Sms
972      *
973      * @param tracker a SmsTracker could hold a multi-part Sms
974      * @return true for tracker holds Multi-parts Sms
975      */
976     private boolean isMultipartTracker (SmsTracker tracker) {
977         HashMap map = tracker.mData;
978         return ( map.get("parts") != null);
979     }
980
981     /**
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.
984      *
985      */
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;
991
992         public PendingIntent mSentIntent;
993         public PendingIntent mDeliveryIntent;
994
995         SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
996                 PendingIntent deliveryIntent) {
997             mData = data;
998             mSentIntent = sentIntent;
999             mDeliveryIntent = deliveryIntent;
1000             mRetryCount = 0;
1001         }
1002     }
1003
1004     protected SmsTracker SmsTrackerFactory(HashMap<String, Object> data, PendingIntent sentIntent,
1005             PendingIntent deliveryIntent) {
1006         return new SmsTracker(data, sentIntent, deliveryIntent);
1007     }
1008
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.");
1014     }
1015
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.");
1021     }
1022
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.");
1028     }
1029
1030     private DialogInterface.OnClickListener mListener =
1031         new DialogInterface.OnClickListener() {
1032
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));
1040                 }
1041             }
1042         };
1043
1044     private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
1045         @Override
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));
1053             } else {
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);
1059
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);
1063             }
1064         }
1065     };
1066
1067     protected abstract void handleBroadcastSms(AsyncResult ar);
1068
1069     protected void dispatchBroadcastPdus(byte[][] pdus) {
1070         Intent intent = new Intent("android.provider.telephony.SMS_CB_RECEIVED");
1071         intent.putExtra("pdus", pdus);
1072
1073         if (Config.LOGD)
1074             Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus");
1075
1076         dispatch(intent, "android.permission.RECEIVE_SMS");
1077     }
1078
1079 }