SmsBroadcastUndelivered.java revision dc7411e9fc8ac029a7bad8b85b0f24642a1774b6
10d4bcdf379842af4b6304809156971e926f374f0Jake Hamby/*
20d4bcdf379842af4b6304809156971e926f374f0Jake Hamby * Copyright (C) 2013 The Android Open Source Project
30d4bcdf379842af4b6304809156971e926f374f0Jake Hamby *
40d4bcdf379842af4b6304809156971e926f374f0Jake Hamby * Licensed under the Apache License, Version 2.0 (the "License");
50d4bcdf379842af4b6304809156971e926f374f0Jake Hamby * you may not use this file except in compliance with the License.
60d4bcdf379842af4b6304809156971e926f374f0Jake Hamby * You may obtain a copy of the License at
70d4bcdf379842af4b6304809156971e926f374f0Jake Hamby *
80d4bcdf379842af4b6304809156971e926f374f0Jake Hamby *      http://www.apache.org/licenses/LICENSE-2.0
90d4bcdf379842af4b6304809156971e926f374f0Jake Hamby *
100d4bcdf379842af4b6304809156971e926f374f0Jake Hamby * Unless required by applicable law or agreed to in writing, software
110d4bcdf379842af4b6304809156971e926f374f0Jake Hamby * distributed under the License is distributed on an "AS IS" BASIS,
120d4bcdf379842af4b6304809156971e926f374f0Jake Hamby * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130d4bcdf379842af4b6304809156971e926f374f0Jake Hamby * See the License for the specific language governing permissions and
140d4bcdf379842af4b6304809156971e926f374f0Jake Hamby * limitations under the License.
150d4bcdf379842af4b6304809156971e926f374f0Jake Hamby */
160d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
170d4bcdf379842af4b6304809156971e926f374f0Jake Hambypackage com.android.internal.telephony;
180d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
198f21e0c6528f015924d153350dd2a717d3b51f66Ji Yangimport android.content.BroadcastReceiver;
200d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport android.content.ContentResolver;
210d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport android.content.Context;
228f21e0c6528f015924d153350dd2a717d3b51f66Ji Yangimport android.content.Intent;
238f21e0c6528f015924d153350dd2a717d3b51f66Ji Yangimport android.content.IntentFilter;
240d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport android.database.Cursor;
250d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport android.database.SQLException;
260d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport android.net.Uri;
278f21e0c6528f015924d153350dd2a717d3b51f66Ji Yangimport android.os.UserHandle;
280d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport android.provider.Telephony;
290d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport android.telephony.Rlog;
300d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
310d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
320d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport com.android.internal.telephony.gsm.GsmInboundSmsHandler;
330d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
340d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport java.util.HashMap;
350d4bcdf379842af4b6304809156971e926f374f0Jake Hambyimport java.util.HashSet;
360d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
370d4bcdf379842af4b6304809156971e926f374f0Jake Hamby/**
388f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang * Called when the credential-encrypted storage is unlocked, collecting all acknowledged messages
398f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang * and deleting any partial message segments older than 30 days. Called from a worker thread to
408f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang * avoid delaying phone app startup. The last step is to broadcast the first pending message from
418f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang * the main thread, then the remaining pending messages will be broadcast after the previous
428f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang * ordered broadcast completes.
430d4bcdf379842af4b6304809156971e926f374f0Jake Hamby */
448f21e0c6528f015924d153350dd2a717d3b51f66Ji Yangpublic class SmsBroadcastUndelivered {
450d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    private static final String TAG = "SmsBroadcastUndelivered";
460d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    private static final boolean DBG = InboundSmsHandler.DBG;
470d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
480d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    /** Delete any partial message segments older than 30 days. */
490d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    static final long PARTIAL_SEGMENT_EXPIRE_AGE = (long) (60 * 60 * 1000) * 24 * 30;
500d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
510d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    /**
520d4bcdf379842af4b6304809156971e926f374f0Jake Hamby     * Query projection for dispatching pending messages at boot time.
530d4bcdf379842af4b6304809156971e926f374f0Jake Hamby     * Column order must match the {@code *_COLUMN} constants in {@link InboundSmsHandler}.
540d4bcdf379842af4b6304809156971e926f374f0Jake Hamby     */
550d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    private static final String[] PDU_PENDING_MESSAGE_PROJECTION = {
560d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            "pdu",
570d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            "sequence",
580d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            "destination_port",
590d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            "date",
600d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            "reference_number",
610d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            "count",
620d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            "address",
630d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            "_id"
640d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    };
650d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
660d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    /** URI for raw table from SmsProvider. */
670d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
688f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang    private static SmsBroadcastUndelivered instance;
690d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
700d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    /** Content resolver to use to access raw table from SmsProvider. */
710d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    private final ContentResolver mResolver;
720d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
730d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    /** Handler for 3GPP-format messages (may be null). */
740d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    private final GsmInboundSmsHandler mGsmInboundSmsHandler;
750d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
760d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    /** Handler for 3GPP2-format messages (may be null). */
770d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    private final CdmaInboundSmsHandler mCdmaInboundSmsHandler;
780d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
798f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang    /** Broadcast receiver that processes the raw table when the user unlocks the phone for the
808f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang     *  first time after reboot and the credential-encrypted storage is available.
818f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang     */
828f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
838f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang        @Override
848f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang        public void onReceive(Context context, Intent intent) {
858f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang            Rlog.d(TAG, "Received broadcast " + intent.getAction());
868f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
878f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang                Thread t = new Thread() {
888f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang                    @Override
898f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang                    public void run() {
908f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang                        scanRawTable();
918f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang                    }
928f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang                };
938f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang                t.start();
948f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang            }
958f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang        }
968f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang    };
978f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang
988f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang    public static void initialize(Context context, GsmInboundSmsHandler gsmInboundSmsHandler,
998f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang        CdmaInboundSmsHandler cdmaInboundSmsHandler) {
1008f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang        if (instance == null) {
1018f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang            instance = new SmsBroadcastUndelivered(
1028f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang                context, gsmInboundSmsHandler, cdmaInboundSmsHandler);
1038f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang        }
104dc7411e9fc8ac029a7bad8b85b0f24642a1774b6Ji Yang
105dc7411e9fc8ac029a7bad8b85b0f24642a1774b6Ji Yang        // Tell handlers to start processing new messages and transit from the startup state to the
106dc7411e9fc8ac029a7bad8b85b0f24642a1774b6Ji Yang        // idle state. This method may be called multiple times for multi-sim devices. We must make
107dc7411e9fc8ac029a7bad8b85b0f24642a1774b6Ji Yang        // sure the state transition happen to all inbound sms handlers.
108dc7411e9fc8ac029a7bad8b85b0f24642a1774b6Ji Yang        if (gsmInboundSmsHandler != null) {
109dc7411e9fc8ac029a7bad8b85b0f24642a1774b6Ji Yang            gsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_START_ACCEPTING_SMS);
110dc7411e9fc8ac029a7bad8b85b0f24642a1774b6Ji Yang        }
111dc7411e9fc8ac029a7bad8b85b0f24642a1774b6Ji Yang        if (cdmaInboundSmsHandler != null) {
112dc7411e9fc8ac029a7bad8b85b0f24642a1774b6Ji Yang            cdmaInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_START_ACCEPTING_SMS);
113dc7411e9fc8ac029a7bad8b85b0f24642a1774b6Ji Yang        }
1148f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang    }
1158f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang
1168f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang    private SmsBroadcastUndelivered(Context context, GsmInboundSmsHandler gsmInboundSmsHandler,
1170d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            CdmaInboundSmsHandler cdmaInboundSmsHandler) {
1180d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        mResolver = context.getContentResolver();
1190d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        mGsmInboundSmsHandler = gsmInboundSmsHandler;
1200d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        mCdmaInboundSmsHandler = cdmaInboundSmsHandler;
1210d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
1228f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang        IntentFilter userFilter = new IntentFilter();
1238f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang        userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
1248f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang        context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
1250d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    }
1260d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
1270d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    /**
1280d4bcdf379842af4b6304809156971e926f374f0Jake Hamby     * Scan the raw table for complete SMS messages to broadcast, and old PDUs to delete.
1290d4bcdf379842af4b6304809156971e926f374f0Jake Hamby     */
1300d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    private void scanRawTable() {
1318f21e0c6528f015924d153350dd2a717d3b51f66Ji Yang        if (DBG) Rlog.d(TAG, "scanning raw table for undelivered messages");
1320d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        long startTime = System.nanoTime();
1330d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        HashMap<SmsReferenceKey, Integer> multiPartReceivedCount =
1340d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                new HashMap<SmsReferenceKey, Integer>(4);
1350d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        HashSet<SmsReferenceKey> oldMultiPartMessages = new HashSet<SmsReferenceKey>(4);
1360d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        Cursor cursor = null;
1370d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        try {
1380d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            cursor = mResolver.query(sRawUri, PDU_PENDING_MESSAGE_PROJECTION, null, null, null);
1390d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            if (cursor == null) {
1400d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                Rlog.e(TAG, "error getting pending message cursor");
1410d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                return;
1420d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            }
1430d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
1440d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            boolean isCurrentFormat3gpp2 = InboundSmsHandler.isCurrentFormat3gpp2();
1450d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            while (cursor.moveToNext()) {
1460d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                InboundSmsTracker tracker;
1470d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                try {
1480d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    tracker = new InboundSmsTracker(cursor, isCurrentFormat3gpp2);
1490d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                } catch (IllegalArgumentException e) {
1500d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    Rlog.e(TAG, "error loading SmsTracker: " + e);
1510d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    continue;
1520d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                }
1530d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
1540d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                if (tracker.getMessageCount() == 1) {
1550d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    // deliver single-part message
1560d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    broadcastSms(tracker);
1570d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                } else {
1580d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    SmsReferenceKey reference = new SmsReferenceKey(tracker);
1590d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    Integer receivedCount = multiPartReceivedCount.get(reference);
1600d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    if (receivedCount == null) {
1610d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                        multiPartReceivedCount.put(reference, 1);    // first segment seen
1620d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                        if (tracker.getTimestamp() <
1630d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                                (System.currentTimeMillis() - PARTIAL_SEGMENT_EXPIRE_AGE)) {
1640d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                            // older than 30 days; delete if we don't find all the segments
1650d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                            oldMultiPartMessages.add(reference);
1660d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                        }
1670d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    } else {
1680d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                        int newCount = receivedCount + 1;
1690d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                        if (newCount == tracker.getMessageCount()) {
1700d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                            // looks like we've got all the pieces; send a single tracker
1710d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                            // to state machine which will find the other pieces to broadcast
1720d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                            if (DBG) Rlog.d(TAG, "found complete multi-part message");
1730d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                            broadcastSms(tracker);
1740d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                            // don't delete this old message until after we broadcast it
1750d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                            oldMultiPartMessages.remove(reference);
1760d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                        } else {
1770d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                            multiPartReceivedCount.put(reference, newCount);
1780d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                        }
1790d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    }
1800d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                }
1810d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            }
1820d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            // Delete old incomplete message segments
1830d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            for (SmsReferenceKey message : oldMultiPartMessages) {
1840d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                int rows = mResolver.delete(sRawUri, InboundSmsHandler.SELECT_BY_REFERENCE,
1850d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                        message.getDeleteWhereArgs());
1860d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                if (rows == 0) {
1870d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    Rlog.e(TAG, "No rows were deleted from raw table!");
1880d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                } else if (DBG) {
1890d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    Rlog.d(TAG, "Deleted " + rows + " rows from raw table for incomplete "
1900d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                            + message.mMessageCount + " part message");
1910d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                }
1920d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            }
1930d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        } catch (SQLException e) {
1940d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            Rlog.e(TAG, "error reading pending SMS messages", e);
1950d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        } finally {
1960d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            if (cursor != null) {
1970d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                cursor.close();
1980d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            }
1990d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            if (DBG) Rlog.d(TAG, "finished scanning raw table in "
2000d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    + ((System.nanoTime() - startTime) / 1000000) + " ms");
2010d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        }
2020d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    }
2030d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
2040d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    /**
2050d4bcdf379842af4b6304809156971e926f374f0Jake Hamby     * Send tracker to appropriate (3GPP or 3GPP2) inbound SMS handler for broadcast.
2060d4bcdf379842af4b6304809156971e926f374f0Jake Hamby     */
2070d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    private void broadcastSms(InboundSmsTracker tracker) {
2080d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        InboundSmsHandler handler;
2090d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        if (tracker.is3gpp2()) {
2100d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            handler = mCdmaInboundSmsHandler;
2110d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        } else {
2120d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            handler = mGsmInboundSmsHandler;
2130d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        }
2140d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        if (handler != null) {
2150d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            handler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS, tracker);
2160d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        } else {
2170d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            Rlog.e(TAG, "null handler for " + tracker.getFormat() + " format, can't deliver.");
2180d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        }
2190d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    }
2200d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
2210d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    /**
2220d4bcdf379842af4b6304809156971e926f374f0Jake Hamby     * Used as the HashMap key for matching concatenated message segments.
2230d4bcdf379842af4b6304809156971e926f374f0Jake Hamby     */
2240d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    private static class SmsReferenceKey {
2250d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        final String mAddress;
2260d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        final int mReferenceNumber;
2270d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        final int mMessageCount;
2280d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
2290d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        SmsReferenceKey(InboundSmsTracker tracker) {
2300d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            mAddress = tracker.getAddress();
2310d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            mReferenceNumber = tracker.getReferenceNumber();
2320d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            mMessageCount = tracker.getMessageCount();
2330d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        }
2340d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
2350d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        String[] getDeleteWhereArgs() {
2360d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            return new String[]{mAddress, Integer.toString(mReferenceNumber),
2370d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                    Integer.toString(mMessageCount)};
2380d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        }
2390d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
2400d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        @Override
2410d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        public int hashCode() {
2420d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            return ((mReferenceNumber * 31) + mMessageCount) * 31 + mAddress.hashCode();
2430d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        }
2440d4bcdf379842af4b6304809156971e926f374f0Jake Hamby
2450d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        @Override
2460d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        public boolean equals(Object o) {
2470d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            if (o instanceof SmsReferenceKey) {
2480d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                SmsReferenceKey other = (SmsReferenceKey) o;
2490d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                return other.mAddress.equals(mAddress)
2500d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                        && (other.mReferenceNumber == mReferenceNumber)
2510d4bcdf379842af4b6304809156971e926f374f0Jake Hamby                        && (other.mMessageCount == mMessageCount);
2520d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            }
2530d4bcdf379842af4b6304809156971e926f374f0Jake Hamby            return false;
2540d4bcdf379842af4b6304809156971e926f374f0Jake Hamby        }
2550d4bcdf379842af4b6304809156971e926f374f0Jake Hamby    }
2560d4bcdf379842af4b6304809156971e926f374f0Jake Hamby}
257