InboundSmsHandler.java revision 8b61e10b480d3a7bae41025a97f3bd27ee5ed610
1/*
2 * Copyright (C) 2013 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
17package com.android.internal.telephony;
18
19import android.app.Activity;
20import android.app.AppOpsManager;
21import android.content.BroadcastReceiver;
22import android.content.ContentResolver;
23import android.content.ContentUris;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.database.Cursor;
28import android.database.SQLException;
29import android.net.Uri;
30import android.os.AsyncResult;
31import android.os.Build;
32import android.os.Message;
33import android.os.PowerManager;
34import android.os.SystemProperties;
35import android.provider.Telephony;
36import android.provider.Telephony.Sms.Intents;
37import android.telephony.Rlog;
38import android.telephony.SmsMessage;
39import android.telephony.TelephonyManager;
40
41import com.android.internal.util.HexDump;
42import com.android.internal.util.State;
43import com.android.internal.util.StateMachine;
44
45import java.io.ByteArrayOutputStream;
46import java.util.Arrays;
47
48import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
49
50/**
51 * This class broadcasts incoming SMS messages to interested apps after storing them in
52 * the SmsProvider "raw" table and ACKing them to the SMSC. After each message has been
53 * broadcast, its parts are removed from the raw table. If the device crashes after ACKing
54 * but before the broadcast completes, the pending messages will be rebroadcast on the next boot.
55 *
56 * <p>The state machine starts in {@link IdleState} state. When the {@link SMSDispatcher} receives a
57 * new SMS from the radio, it calls {@link #dispatchNormalMessage},
58 * which sends a message to the state machine, causing the wakelock to be acquired in
59 * {@link #haltedProcessMessage}, which transitions to {@link DeliveringState} state, where the message
60 * is saved to the raw table, then acknowledged via the {@link SMSDispatcher} which called us.
61 *
62 * <p>After saving the SMS, if the message is complete (either single-part or the final segment
63 * of a multi-part SMS), we broadcast the completed PDUs as an ordered broadcast, then transition to
64 * {@link WaitingState} state to wait for the broadcast to complete. When the local
65 * {@link BroadcastReceiver} is called with the result, it sends {@link #EVENT_BROADCAST_COMPLETE}
66 * to the state machine, causing us to either broadcast the next pending message (if one has
67 * arrived while waiting for the broadcast to complete), or to transition back to the halted state
68 * after all messages are processed. Then the wakelock is released and we wait for the next SMS.
69 */
70public abstract class InboundSmsHandler extends StateMachine {
71    protected static final boolean DBG = true;
72    private static final boolean VDBG = false;  // STOPSHIP if true, logs user data
73
74    /** Query projection for checking for duplicate message segments. */
75    private static final String[] PDU_PROJECTION = {
76            "pdu"
77    };
78
79    /** Query projection for combining concatenated message segments. */
80    private static final String[] PDU_SEQUENCE_PORT_PROJECTION = {
81            "pdu",
82            "sequence",
83            "destination_port"
84    };
85
86    static final int PDU_COLUMN = 0;
87    static final int SEQUENCE_COLUMN = 1;
88    static final int DESTINATION_PORT_COLUMN = 2;
89    static final int DATE_COLUMN = 3;
90    static final int REFERENCE_NUMBER_COLUMN = 4;
91    static final int COUNT_COLUMN = 5;
92    static final int ADDRESS_COLUMN = 6;
93    static final int ID_COLUMN = 7;
94
95    static final String SELECT_BY_ID = "_id=?";
96    static final String SELECT_BY_REFERENCE = "address=? AND reference_number=? AND count=?";
97
98    /** New SMS received as an AsyncResult. */
99    public static final int EVENT_NEW_SMS = 1;
100
101    /** Message type containing a {@link InboundSmsTracker} ready to broadcast to listeners. */
102    static final int EVENT_BROADCAST_SMS = 2;
103
104    /** Message from resultReceiver notifying {@link WaitingState} of a completed broadcast. */
105    static final int EVENT_BROADCAST_COMPLETE = 3;
106
107    /** Sent on exit from {@link WaitingState} to return to idle after sending all broadcasts. */
108    static final int EVENT_RETURN_TO_IDLE = 4;
109
110    /** Release wakelock after a short timeout when returning to idle state. */
111    static final int EVENT_RELEASE_WAKELOCK = 5;
112
113    /** Sent by {@link SmsBroadcastUndelivered} after cleaning the raw table. */
114    static final int EVENT_START_ACCEPTING_SMS = 6;
115
116    /** Wakelock release delay when returning to idle state. */
117    private static final int WAKELOCK_TIMEOUT = 3000;
118
119    /** URI for raw table of SMS provider. */
120    private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
121
122    protected final Context mContext;
123    private final ContentResolver mResolver;
124
125    /** Special handler for WAP push messages. */
126    private final WapPushOverSms mWapPush;
127
128    /** Wake lock to ensure device stays awake while dispatching the SMS intents. */
129    final PowerManager.WakeLock mWakeLock;
130
131    /** DefaultState throws an exception or logs an error for unhandled message types. */
132    final DefaultState mDefaultState = new DefaultState();
133
134    /** Startup state. Waiting for {@link SmsBroadcastUndelivered} to complete. */
135    final StartupState mStartupState = new StartupState();
136
137    /** Idle state. Waiting for messages to process. */
138    final IdleState mIdleState = new IdleState();
139
140    /** Delivering state. Saves the PDU in the raw table and acknowledges to SMSC. */
141    final DeliveringState mDeliveringState = new DeliveringState();
142
143    /** Broadcasting state. Waits for current broadcast to complete before delivering next. */
144    final WaitingState mWaitingState = new WaitingState();
145
146    /** Helper class to check whether storage is available for incoming messages. */
147    protected final SmsStorageMonitor mStorageMonitor;
148
149    private final boolean mSmsReceiveDisabled;
150
151    /**
152     * Create a new SMS broadcast helper.
153     * @param name the class name for logging
154     * @param context the context of the phone app
155     * @param storageMonitor the SmsStorageMonitor to check for storage availability
156     */
157    protected InboundSmsHandler(String name, Context context, SmsStorageMonitor storageMonitor) {
158        super(name);
159
160        mContext = context;
161        mStorageMonitor = storageMonitor;
162        mResolver = context.getContentResolver();
163        mWapPush = new WapPushOverSms(context);
164
165        boolean smsCapable = mContext.getResources().getBoolean(
166                com.android.internal.R.bool.config_sms_capable);
167        mSmsReceiveDisabled = !SystemProperties.getBoolean(
168                TelephonyProperties.PROPERTY_SMS_RECEIVE, smsCapable);
169
170        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
171        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
172        mWakeLock.acquire();    // wake lock released after we enter idle state
173
174        addState(mDefaultState);
175        addState(mStartupState, mDefaultState);
176        addState(mIdleState, mDefaultState);
177        addState(mDeliveringState, mDefaultState);
178            addState(mWaitingState, mDeliveringState);
179
180        setInitialState(mStartupState);
181        if (DBG) log("created InboundSmsHandler");
182    }
183
184    /**
185     * Tell the state machine to quit after processing all messages.
186     */
187    public void dispose() {
188        quit();
189    }
190
191    /**
192     * Dispose of the WAP push object and release the wakelock.
193     */
194    @Override
195    protected void onQuitting() {
196        mWapPush.dispose();
197
198        while (mWakeLock.isHeld()) {
199            mWakeLock.release();
200        }
201    }
202
203    /**
204     * This parent state throws an exception (for debug builds) or prints an error for unhandled
205     * message types.
206     */
207    class DefaultState extends State {
208        @Override
209        public boolean processMessage(Message msg) {
210            String errorText = "processMessage: unhandled message type " + msg.what;
211            if (Build.IS_DEBUGGABLE) {
212                throw new RuntimeException(errorText);
213              } else {
214                loge(errorText);
215                return HANDLED;
216              }
217        }
218    }
219
220    /**
221     * The Startup state waits for {@link SmsBroadcastUndelivered} to process the raw table and
222     * notify the state machine to broadcast any complete PDUs that might not have been broadcast.
223     */
224    class StartupState extends State {
225        @Override
226        public boolean processMessage(Message msg) {
227            switch (msg.what) {
228                case EVENT_NEW_SMS:
229                case EVENT_BROADCAST_SMS:
230                    deferMessage(msg);
231                    return HANDLED;
232
233                case EVENT_START_ACCEPTING_SMS:
234                    transitionTo(mIdleState);
235                    return HANDLED;
236
237                case EVENT_BROADCAST_COMPLETE:
238                case EVENT_RETURN_TO_IDLE:
239                case EVENT_RELEASE_WAKELOCK:
240                default:
241                    // let DefaultState handle these unexpected message types
242                    return NOT_HANDLED;
243            }
244        }
245    }
246
247    /**
248     * In the idle state the wakelock is released until a new SM arrives, then we transition
249     * to Delivering mode to handle it, acquiring the wakelock on exit.
250     */
251    class IdleState extends State {
252        @Override
253        public void enter() {
254            if (DBG) log("entering Idle state");
255            sendMessageDelayed(EVENT_RELEASE_WAKELOCK, WAKELOCK_TIMEOUT);
256        }
257
258        @Override
259        public void exit() {
260            mWakeLock.acquire();
261            if (DBG) log("acquired wakelock, leaving Idle state");
262        }
263
264        @Override
265        public boolean processMessage(Message msg) {
266            if (DBG) log("Idle state processing message type " + msg.what);
267            switch (msg.what) {
268                case EVENT_NEW_SMS:
269                case EVENT_BROADCAST_SMS:
270                    deferMessage(msg);
271                    transitionTo(mDeliveringState);
272                    return HANDLED;
273
274                case EVENT_RELEASE_WAKELOCK:
275                    mWakeLock.release();
276                    if (DBG) {
277                        if (mWakeLock.isHeld()) {
278                            // this is okay as long as we call release() for every acquire()
279                            log("mWakeLock is still held after release");
280                        } else {
281                            log("mWakeLock released");
282                        }
283                    }
284                    return HANDLED;
285
286                case EVENT_RETURN_TO_IDLE:
287                    // already in idle state; ignore
288                    return HANDLED;
289
290                case EVENT_BROADCAST_COMPLETE:
291                case EVENT_START_ACCEPTING_SMS:
292                default:
293                    // let DefaultState handle these unexpected message types
294                    return NOT_HANDLED;
295            }
296        }
297    }
298
299    /**
300     * In the delivering state, the inbound SMS is processed and stored in the raw table.
301     * The message is acknowledged before we exit this state. If there is a message to broadcast,
302     * transition to {@link WaitingState} state to send the ordered broadcast and wait for the
303     * results. When all messages have been processed, the halting state will release the wakelock.
304     */
305    class DeliveringState extends State {
306        @Override
307        public void enter() {
308            if (DBG) log("entering Delivering state");
309        }
310
311        @Override
312        public void exit() {
313            if (DBG) log("leaving Delivering state");
314        }
315
316        @Override
317        public boolean processMessage(Message msg) {
318            switch (msg.what) {
319                case EVENT_NEW_SMS:
320                    // handle new SMS from RIL
321                    handleNewSms((AsyncResult) msg.obj);
322                    sendMessage(EVENT_RETURN_TO_IDLE);
323                    return HANDLED;
324
325                case EVENT_BROADCAST_SMS:
326                    // if any broadcasts were sent, transition to waiting state
327                    if (processMessagePart((InboundSmsTracker) msg.obj)) {
328                        transitionTo(mWaitingState);
329                    }
330                    return HANDLED;
331
332                case EVENT_RETURN_TO_IDLE:
333                    // return to idle after processing all other messages
334                    transitionTo(mIdleState);
335                    return HANDLED;
336
337                case EVENT_RELEASE_WAKELOCK:
338                    mWakeLock.release();    // decrement wakelock from previous entry to Idle
339                    if (!mWakeLock.isHeld()) {
340                        // wakelock should still be held until 3 seconds after we enter Idle
341                        loge("mWakeLock released while delivering/broadcasting!");
342                    }
343                    return HANDLED;
344
345                // we shouldn't get this message type in this state, log error and halt.
346                case EVENT_BROADCAST_COMPLETE:
347                case EVENT_START_ACCEPTING_SMS:
348                default:
349                    // let DefaultState handle these unexpected message types
350                    return NOT_HANDLED;
351            }
352        }
353    }
354
355    /**
356     * The waiting state delegates handling of new SMS to parent {@link DeliveringState}, but
357     * defers handling of the {@link #EVENT_BROADCAST_SMS} phase until after the current
358     * result receiver sends {@link #EVENT_BROADCAST_COMPLETE}. Before transitioning to
359     * {@link DeliveringState}, {@link #EVENT_RETURN_TO_IDLE} is sent to transition to
360     * {@link IdleState} after any deferred {@link #EVENT_BROADCAST_SMS} messages are handled.
361     */
362    class WaitingState extends State {
363        @Override
364        public boolean processMessage(Message msg) {
365            switch (msg.what) {
366                case EVENT_BROADCAST_SMS:
367                    // defer until the current broadcast completes
368                    deferMessage(msg);
369                    return HANDLED;
370
371                case EVENT_BROADCAST_COMPLETE:
372                    // return to idle after handling all deferred messages
373                    sendMessage(EVENT_RETURN_TO_IDLE);
374                    transitionTo(mDeliveringState);
375                    return HANDLED;
376
377                case EVENT_RETURN_TO_IDLE:
378                    // not ready to return to idle; ignore
379                    return HANDLED;
380
381                default:
382                    // parent state handles the other message types
383                    return NOT_HANDLED;
384            }
385        }
386    }
387
388    void handleNewSms(AsyncResult ar) {
389        if (ar.exception != null) {
390            loge("Exception processing incoming SMS: " + ar.exception);
391            return;
392        }
393
394        int result;
395        try {
396            SmsMessage sms = (SmsMessage) ar.result;
397            result = dispatchMessage(sms.mWrappedSmsMessage);
398        } catch (RuntimeException ex) {
399            loge("Exception dispatching message", ex);
400            result = Intents.RESULT_SMS_GENERIC_ERROR;
401        }
402
403        // RESULT_OK means that the SMS will be acknowledged by special handling,
404        // e.g. for SMS-PP data download. Any other result, we should ack here.
405        if (result != Activity.RESULT_OK) {
406            boolean handled = (result == Intents.RESULT_SMS_HANDLED);
407            notifyAndAcknowledgeLastIncomingSms(handled, result, null);
408        }
409    }
410
411    /**
412     * Process an SMS message from the RIL, calling subclass methods to handle 3GPP and
413     * 3GPP2-specific message types.
414     *
415     * @param smsb the SmsMessageBase object from the RIL
416     * @return a result code from {@link android.provider.Telephony.Sms.Intents},
417     *  or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC
418     */
419    public int dispatchMessage(SmsMessageBase smsb) {
420        // If sms is null, there was a parsing error.
421        if (smsb == null) {
422            loge("dispatchSmsMessage: message is null");
423            return Intents.RESULT_SMS_GENERIC_ERROR;
424        }
425
426        if (mSmsReceiveDisabled) {
427            // Device doesn't support receiving SMS,
428            log("Received short message on device which doesn't support "
429                    + "receiving SMS. Ignored.");
430            return Intents.RESULT_SMS_HANDLED;
431        }
432
433        return dispatchMessageRadioSpecific(smsb);
434    }
435
436    /**
437     * Process voicemail notification, SMS-PP data download, CDMA CMAS, CDMA WAP push, and other
438     * 3GPP/3GPP2-specific messages. Regular SMS messages are handled by calling the shared
439     * {@link #dispatchNormalMessage} from this class.
440     *
441     * @param smsb the SmsMessageBase object from the RIL
442     * @return a result code from {@link android.provider.Telephony.Sms.Intents},
443     *  or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC
444     */
445    protected abstract int dispatchMessageRadioSpecific(SmsMessageBase smsb);
446
447    /**
448     * Send an acknowledge message to the SMSC.
449     * @param success indicates that last message was successfully received.
450     * @param result result code indicating any error
451     * @param response callback message sent when operation completes.
452     */
453    protected abstract void acknowledgeLastIncomingSms(boolean success,
454            int result, Message response);
455
456    /**
457     * Notify interested apps if the framework has rejected an incoming SMS,
458     * and send an acknowledge message to the network.
459     * @param success indicates that last message was successfully received.
460     * @param result result code indicating any error
461     * @param response callback message sent when operation completes.
462     */
463    void notifyAndAcknowledgeLastIncomingSms(boolean success,
464            int result, Message response) {
465        if (!success) {
466            // broadcast SMS_REJECTED_ACTION intent
467            Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
468            intent.putExtra("result", result);
469            mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
470        }
471        acknowledgeLastIncomingSms(success, result, response);
472    }
473
474    /**
475     * Return true if this handler is for 3GPP2 messages; false for 3GPP format.
476     * @return true for the 3GPP2 handler; false for the 3GPP handler
477     */
478    protected abstract boolean is3gpp2();
479
480    /**
481     * Dispatch a normal incoming SMS. This is called from {@link #dispatchMessageRadioSpecific}
482     * if no format-specific handling was required. Saves the PDU to the SMS provider raw table,
483     * creates an {@link InboundSmsTracker}, then sends it to the state machine as an
484     * {@link #EVENT_BROADCAST_SMS}. Returns {@link Intents#RESULT_SMS_HANDLED} or an error value.
485     *
486     * @param sms the message to dispatch
487     * @return {@link Intents#RESULT_SMS_HANDLED} if the message was accepted, or an error status
488     */
489    protected int dispatchNormalMessage(SmsMessageBase sms) {
490        SmsHeader smsHeader = sms.getUserDataHeader();
491        InboundSmsTracker tracker;
492
493        if ((smsHeader == null) || (smsHeader.concatRef == null)) {
494            // Message is not concatenated.
495            int destPort = -1;
496            if (smsHeader != null && smsHeader.portAddrs != null) {
497                // The message was sent to a port.
498                destPort = smsHeader.portAddrs.destPort;
499                if (DBG) log("destination port: " + destPort);
500            }
501
502            tracker = new InboundSmsTracker(sms.getPdu(), sms.getTimestampMillis(), destPort,
503                    is3gpp2(), false);
504        } else {
505            // Create a tracker for this message segment.
506            SmsHeader.ConcatRef concatRef = smsHeader.concatRef;
507            SmsHeader.PortAddrs portAddrs = smsHeader.portAddrs;
508            int destPort = (portAddrs != null ? portAddrs.destPort : -1);
509
510            tracker = new InboundSmsTracker(sms.getPdu(), sms.getTimestampMillis(), destPort,
511                    is3gpp2(), sms.getOriginatingAddress(), concatRef.refNumber,
512                    concatRef.seqNumber, concatRef.msgCount, false);
513        }
514
515        if (VDBG) log("created tracker: " + tracker);
516        return addTrackerToRawTableAndSendMessage(tracker);
517    }
518
519    /**
520     * Helper to add the tracker to the raw table and then send a message to broadcast it, if
521     * successful. Returns the SMS intent status to return to the SMSC.
522     * @param tracker the tracker to save to the raw table and then deliver
523     * @return {@link Intents#RESULT_SMS_HANDLED} or {@link Intents#RESULT_SMS_GENERIC_ERROR}
524     * or {@link Intents#RESULT_SMS_DUPLICATED}
525     */
526    protected int addTrackerToRawTableAndSendMessage(InboundSmsTracker tracker) {
527        switch(addTrackerToRawTable(tracker)) {
528        case Intents.RESULT_SMS_HANDLED:
529            sendMessage(EVENT_BROADCAST_SMS, tracker);
530            return Intents.RESULT_SMS_HANDLED;
531
532        case Intents.RESULT_SMS_DUPLICATED:
533            return Intents.RESULT_SMS_HANDLED;
534
535        case Intents.RESULT_SMS_GENERIC_ERROR:
536        default:
537            return Intents.RESULT_SMS_GENERIC_ERROR;
538        }
539    }
540
541    /**
542     * Process the inbound SMS segment. If the message is complete, send it as an ordered
543     * broadcast to interested receivers and return true. If the message is a segment of an
544     * incomplete multi-part SMS, return false.
545     * @param tracker the tracker containing the message segment to process
546     * @return true if an ordered broadcast was sent; false if waiting for more message segments
547     */
548    boolean processMessagePart(InboundSmsTracker tracker) {
549        int messageCount = tracker.getMessageCount();
550        byte[][] pdus;
551        int destPort = tracker.getDestPort();
552
553        if (messageCount == 1) {
554            // single-part message
555            pdus = new byte[][]{tracker.getPdu()};
556        } else {
557            // multi-part message
558            Cursor cursor = null;
559            try {
560                // used by several query selection arguments
561                String address = tracker.getAddress();
562                String refNumber = Integer.toString(tracker.getReferenceNumber());
563                String count = Integer.toString(tracker.getMessageCount());
564
565                // query for all segments and broadcast message if we have all the parts
566                String[] whereArgs = {address, refNumber, count};
567                cursor = mResolver.query(sRawUri, PDU_SEQUENCE_PORT_PROJECTION,
568                        SELECT_BY_REFERENCE, whereArgs, null);
569
570                int cursorCount = cursor.getCount();
571                if (cursorCount < messageCount) {
572                    // Wait for the other message parts to arrive. It's also possible for the last
573                    // segment to arrive before processing the EVENT_BROADCAST_SMS for one of the
574                    // earlier segments. In that case, the broadcast will be sent as soon as all
575                    // segments are in the table, and any later EVENT_BROADCAST_SMS messages will
576                    // get a row count of 0 and return.
577                    return false;
578                }
579
580                // All the parts are in place, deal with them
581                pdus = new byte[messageCount][];
582                while (cursor.moveToNext()) {
583                    // subtract offset to convert sequence to 0-based array index
584                    int index = cursor.getInt(SEQUENCE_COLUMN) - tracker.getIndexOffset();
585
586                    pdus[index] = HexDump.hexStringToByteArray(cursor.getString(PDU_COLUMN));
587
588                    // Read the destination port from the first segment (needed for CDMA WAP PDU).
589                    // It's not a bad idea to prefer the port from the first segment in other cases.
590                    if (index == 0 && !cursor.isNull(DESTINATION_PORT_COLUMN)) {
591                        int port = cursor.getInt(DESTINATION_PORT_COLUMN);
592                        // strip format flags and convert to real port number, or -1
593                        port = InboundSmsTracker.getRealDestPort(port);
594                        if (port != -1) {
595                            destPort = port;
596                        }
597                    }
598                }
599            } catch (SQLException e) {
600                loge("Can't access multipart SMS database", e);
601                return false;
602            } finally {
603                if (cursor != null) {
604                    cursor.close();
605                }
606            }
607        }
608
609        BroadcastReceiver resultReceiver = new SmsBroadcastReceiver(tracker);
610
611        if (destPort == SmsHeader.PORT_WAP_PUSH) {
612            // Build up the data stream
613            ByteArrayOutputStream output = new ByteArrayOutputStream();
614            for (byte[] pdu : pdus) {
615                // 3GPP needs to extract the User Data from the PDU; 3GPP2 has already done this
616                if (!tracker.is3gpp2()) {
617                    SmsMessage msg = SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP);
618                    pdu = msg.getUserData();
619                }
620                output.write(pdu, 0, pdu.length);
621            }
622            int result = mWapPush.dispatchWapPdu(output.toByteArray(), resultReceiver, this);
623            if (DBG) log("dispatchWapPdu() returned " + result);
624            // result is Activity.RESULT_OK if an ordered broadcast was sent
625            return (result == Activity.RESULT_OK);
626        }
627
628        Intent intent;
629        if (destPort == -1) {
630            intent = new Intent(Intents.SMS_RECEIVED_ACTION);
631        } else {
632            Uri uri = Uri.parse("sms://localhost:" + destPort);
633            intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
634        }
635        intent.putExtra("pdus", pdus);
636        intent.putExtra("format", tracker.getFormat());
637        dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
638                AppOpsManager.OP_RECEIVE_SMS, resultReceiver);
639        return true;
640    }
641
642    /**
643     * Dispatch the intent with the specified permission, appOp, and result receiver, using
644     * this state machine's handler thread to run the result receiver.
645     *
646     * @param intent the intent to broadcast
647     * @param permission receivers are required to have this permission
648     * @param appOp app op that is being performed when dispatching to a receiver
649     */
650    void dispatchIntent(Intent intent, String permission, int appOp,
651            BroadcastReceiver resultReceiver) {
652        mContext.sendOrderedBroadcast(intent, permission, appOp, resultReceiver,
653                getHandler(), Activity.RESULT_OK, null, null);
654    }
655
656    /**
657     * Helper for {@link SmsBroadcastUndelivered} to delete an old message in the raw table.
658     */
659    void deleteFromRawTable(String deleteWhere, String[] deleteWhereArgs) {
660        int rows = mResolver.delete(sRawUri, deleteWhere, deleteWhereArgs);
661        if (rows == 0) {
662            loge("No rows were deleted from raw table!");
663        } else if (DBG) {
664            log("Deleted " + rows + " rows from raw table.");
665        }
666    }
667
668    /**
669     * Insert a message PDU into the raw table so we can acknowledge it immediately.
670     * If the device crashes before the broadcast to listeners completes, it will be delivered
671     * from the raw table on the next device boot. For single-part messages, the deleteWhere
672     * and deleteWhereArgs fields of the tracker will be set to delete the correct row after
673     * the ordered broadcast completes.
674     *
675     * @param tracker the tracker to add to the raw table
676     * @return true on success; false on failure to write to database
677     */
678    private int addTrackerToRawTable(InboundSmsTracker tracker) {
679        if (tracker.getMessageCount() != 1) {
680            // check for duplicate message segments
681            Cursor cursor = null;
682            try {
683                // sequence numbers are 1-based except for CDMA WAP, which is 0-based
684                int sequence = tracker.getSequenceNumber();
685
686                // convert to strings for query
687                String address = tracker.getAddress();
688                String refNumber = Integer.toString(tracker.getReferenceNumber());
689                String count = Integer.toString(tracker.getMessageCount());
690
691                String seqNumber = Integer.toString(sequence);
692
693                // set the delete selection args for multi-part message
694                String[] deleteWhereArgs = {address, refNumber, count};
695                tracker.setDeleteWhere(SELECT_BY_REFERENCE, deleteWhereArgs);
696
697                // Check for duplicate message segments
698                cursor = mResolver.query(sRawUri, PDU_PROJECTION,
699                        "address=? AND reference_number=? AND count=? AND sequence=?",
700                        new String[] {address, refNumber, count, seqNumber}, null);
701
702                // moveToNext() returns false if no duplicates were found
703                if (cursor.moveToNext()) {
704                    loge("Discarding duplicate message segment, refNumber=" + refNumber
705                            + " seqNumber=" + seqNumber);
706                    String oldPduString = cursor.getString(PDU_COLUMN);
707                    byte[] pdu = tracker.getPdu();
708                    byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString);
709                    if (!Arrays.equals(oldPdu, tracker.getPdu())) {
710                        loge("Warning: dup message segment PDU of length " + pdu.length
711                                + " is different from existing PDU of length " + oldPdu.length);
712                    }
713                    return Intents.RESULT_SMS_DUPLICATED;   // reject message
714                }
715                cursor.close();
716            } catch (SQLException e) {
717                loge("Can't access multipart SMS database", e);
718                return Intents.RESULT_SMS_GENERIC_ERROR;    // reject message
719            } finally {
720                if (cursor != null) {
721                    cursor.close();
722                }
723            }
724        }
725
726        ContentValues values = tracker.getContentValues();
727
728        if (VDBG) log("adding content values to raw table: " + values.toString());
729        Uri newUri = mResolver.insert(sRawUri, values);
730        if (DBG) log("URI of new row -> " + newUri);
731
732        try {
733            long rowId = ContentUris.parseId(newUri);
734            if (tracker.getMessageCount() == 1) {
735                // set the delete selection args for single-part message
736                tracker.setDeleteWhere(SELECT_BY_ID, new String[]{Long.toString(rowId)});
737            }
738            return Intents.RESULT_SMS_HANDLED;
739        } catch (Exception e) {
740            loge("error parsing URI for new row: " + newUri, e);
741            return Intents.RESULT_SMS_GENERIC_ERROR;
742        }
743    }
744
745    /**
746     * Returns whether the default message format for the current radio technology is 3GPP2.
747     * @return true if the radio technology uses 3GPP2 format by default, false for 3GPP format
748     */
749    static boolean isCurrentFormat3gpp2() {
750        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
751        return (PHONE_TYPE_CDMA == activePhone);
752    }
753
754    /**
755     * Handler for an {@link InboundSmsTracker} broadcast. Deletes PDUs from the raw table and
756     * logs the broadcast duration (as an error if the other receivers were especially slow).
757     */
758    private final class SmsBroadcastReceiver extends BroadcastReceiver {
759        private final String mDeleteWhere;
760        private final String[] mDeleteWhereArgs;
761        private long mBroadcastTimeNano;
762
763        SmsBroadcastReceiver(InboundSmsTracker tracker) {
764            mDeleteWhere = tracker.getDeleteWhere();
765            mDeleteWhereArgs = tracker.getDeleteWhereArgs();
766            mBroadcastTimeNano = System.nanoTime();
767        }
768
769        @Override
770        public void onReceive(Context context, Intent intent) {
771            String action = intent.getAction();
772            if (!Intents.SMS_RECEIVED_ACTION.equals(action)
773                    && !Intents.DATA_SMS_RECEIVED_ACTION.equals(action)) {
774                loge("unexpected BroadcastReceiver action: " + action);
775            }
776
777            int rc = getResultCode();
778            if ((rc != Activity.RESULT_OK) && (rc != Intents.RESULT_SMS_HANDLED)) {
779                loge("a broadcast receiver set the result code to " + rc
780                        + ", deleting from raw table anyway!");
781            } else if (DBG) {
782                log("successful broadcast, deleting from raw table.");
783            }
784
785            deleteFromRawTable(mDeleteWhere, mDeleteWhereArgs);
786            sendMessage(EVENT_BROADCAST_COMPLETE);
787
788            int durationMillis = (int) ((System.nanoTime() - mBroadcastTimeNano) / 1000000);
789            if (durationMillis >= 5000) {
790                loge("Slow ordered broadcast completion time: " + durationMillis + " ms");
791            } else if (DBG) {
792                log("ordered broadcast completed in: " + durationMillis + " ms");
793            }
794        }
795    }
796
797    /**
798     * Log with debug level.
799     * @param s the string to log
800     */
801    @Override
802    protected void log(String s) {
803        Rlog.d(getName(), s);
804    }
805
806    /**
807     * Log with error level.
808     * @param s the string to log
809     */
810    @Override
811    protected void loge(String s) {
812        Rlog.e(getName(), s);
813    }
814
815    /**
816     * Log with error level.
817     * @param s the string to log
818     * @param e is a Throwable which logs additional information.
819     */
820    @Override
821    protected void loge(String s, Throwable e) {
822        Rlog.e(getName(), s, e);
823    }
824}
825