GsmSMSDispatcher.java revision cbaa45bbf2cab852b6c9c3a887e9f803d4e857ea
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
17package com.android.internal.telephony.gsm;
18
19import android.app.Activity;
20import android.app.PendingIntent;
21import android.app.PendingIntent.CanceledException;
22import android.content.Intent;
23import android.os.AsyncResult;
24import android.os.Message;
25import android.os.SystemProperties;
26import android.provider.Telephony.Sms;
27import android.provider.Telephony.Sms.Intents;
28import android.telephony.PhoneNumberUtils;
29import android.telephony.SmsCbLocation;
30import android.telephony.SmsCbMessage;
31import android.telephony.SmsManager;
32import android.telephony.gsm.GsmCellLocation;
33import android.telephony.Rlog;
34
35import com.android.internal.telephony.CommandsInterface;
36import com.android.internal.telephony.GsmAlphabet;
37import com.android.internal.telephony.PhoneBase;
38import com.android.internal.telephony.SmsConstants;
39import com.android.internal.telephony.SMSDispatcher;
40import com.android.internal.telephony.SmsHeader;
41import com.android.internal.telephony.SmsMessageBase;
42import com.android.internal.telephony.SmsStorageMonitor;
43import com.android.internal.telephony.SmsUsageMonitor;
44import com.android.internal.telephony.TelephonyProperties;
45import com.android.internal.telephony.uicc.IccUtils;
46import com.android.internal.telephony.uicc.UsimServiceTable;
47
48import java.util.HashMap;
49import java.util.Iterator;
50
51public final class GsmSMSDispatcher extends SMSDispatcher {
52    private static final String TAG = "GsmSMSDispatcher";
53    private static final boolean VDBG = false;
54
55    /** Status report received */
56    private static final int EVENT_NEW_SMS_STATUS_REPORT = 100;
57
58    /** New broadcast SMS */
59    private static final int EVENT_NEW_BROADCAST_SMS = 101;
60
61    /** Result of writing SM to UICC (when SMS-PP service is not available). */
62    private static final int EVENT_WRITE_SMS_COMPLETE = 102;
63
64    /** Handler for SMS-PP data download messages to UICC. */
65    private final UsimDataDownloadHandler mDataDownloadHandler;
66
67    public GsmSMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor,
68            SmsUsageMonitor usageMonitor) {
69        super(phone, storageMonitor, usageMonitor);
70        mDataDownloadHandler = new UsimDataDownloadHandler(mCm);
71        mCm.setOnNewGsmSms(this, EVENT_NEW_SMS, null);
72        mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
73        mCm.setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null);
74    }
75
76    @Override
77    public void dispose() {
78        mCm.unSetOnNewGsmSms(this);
79        mCm.unSetOnSmsStatus(this);
80        mCm.unSetOnNewGsmBroadcastSms(this);
81    }
82
83    @Override
84    protected String getFormat() {
85        return SmsConstants.FORMAT_3GPP;
86    }
87
88    /**
89     * Handles 3GPP format-specific events coming from the phone stack.
90     * Other events are handled by {@link SMSDispatcher#handleMessage}.
91     *
92     * @param msg the message to handle
93     */
94    @Override
95    public void handleMessage(Message msg) {
96        switch (msg.what) {
97        case EVENT_NEW_SMS_STATUS_REPORT:
98            handleStatusReport((AsyncResult) msg.obj);
99            break;
100
101        case EVENT_NEW_BROADCAST_SMS:
102            handleBroadcastSms((AsyncResult)msg.obj);
103            break;
104
105        case EVENT_WRITE_SMS_COMPLETE:
106            AsyncResult ar = (AsyncResult) msg.obj;
107            if (ar.exception == null) {
108                Rlog.d(TAG, "Successfully wrote SMS-PP message to UICC");
109                mCm.acknowledgeLastIncomingGsmSms(true, 0, null);
110            } else {
111                Rlog.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception);
112                mCm.acknowledgeLastIncomingGsmSms(false,
113                        CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null);
114            }
115            break;
116
117        default:
118            super.handleMessage(msg);
119        }
120    }
121
122    /**
123     * Called when a status report is received.  This should correspond to
124     * a previously successful SEND.
125     *
126     * @param ar AsyncResult passed into the message handler.  ar.result should
127     *           be a String representing the status report PDU, as ASCII hex.
128     */
129    private void handleStatusReport(AsyncResult ar) {
130        String pduString = (String) ar.result;
131        SmsMessage sms = SmsMessage.newFromCDS(pduString);
132
133        if (sms != null) {
134            int tpStatus = sms.getStatus();
135            int messageRef = sms.messageRef;
136            for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
137                SmsTracker tracker = deliveryPendingList.get(i);
138                if (tracker.mMessageRef == messageRef) {
139                    // Found it.  Remove from list and broadcast.
140                    if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) {
141                       deliveryPendingList.remove(i);
142                    }
143                    PendingIntent intent = tracker.mDeliveryIntent;
144                    Intent fillIn = new Intent();
145                    fillIn.putExtra("pdu", IccUtils.hexStringToBytes(pduString));
146                    fillIn.putExtra("format", SmsConstants.FORMAT_3GPP);
147                    try {
148                        intent.send(mContext, Activity.RESULT_OK, fillIn);
149                    } catch (CanceledException ex) {}
150
151                    // Only expect to see one tracker matching this messageref
152                    break;
153                }
154            }
155        }
156        acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null);
157    }
158
159    /** {@inheritDoc} */
160    @Override
161    public int dispatchMessage(SmsMessageBase smsb) {
162
163        // If sms is null, means there was a parsing error.
164        if (smsb == null) {
165            Rlog.e(TAG, "dispatchMessage: message is null");
166            return Intents.RESULT_SMS_GENERIC_ERROR;
167        }
168
169        SmsMessage sms = (SmsMessage) smsb;
170
171        if (sms.isTypeZero()) {
172            // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be
173            // Displayed/Stored/Notified. They should only be acknowledged.
174            Rlog.d(TAG, "Received short message type 0, Don't display or store it. Send Ack");
175            return Intents.RESULT_SMS_HANDLED;
176        }
177
178        // Send SMS-PP data download messages to UICC. See 3GPP TS 31.111 section 7.1.1.
179        if (sms.isUsimDataDownload()) {
180            UsimServiceTable ust = mPhone.getUsimServiceTable();
181            // If we receive an SMS-PP message before the UsimServiceTable has been loaded,
182            // assume that the data download service is not present. This is very unlikely to
183            // happen because the IMS connection will not be established until after the ISIM
184            // records have been loaded, after the USIM service table has been loaded.
185            if (ust != null && ust.isAvailable(
186                    UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) {
187                Rlog.d(TAG, "Received SMS-PP data download, sending to UICC.");
188                return mDataDownloadHandler.startDataDownload(sms);
189            } else {
190                Rlog.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC.");
191                String smsc = IccUtils.bytesToHexString(
192                        PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
193                                sms.getServiceCenterAddress()));
194                mCm.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc,
195                        IccUtils.bytesToHexString(sms.getPdu()),
196                        obtainMessage(EVENT_WRITE_SMS_COMPLETE));
197                return Activity.RESULT_OK;  // acknowledge after response from write to USIM
198            }
199        }
200
201        if (mSmsReceiveDisabled) {
202            // Device doesn't support SMS service,
203            Rlog.d(TAG, "Received short message on device which doesn't support "
204                    + "SMS service. Ignored.");
205            return Intents.RESULT_SMS_HANDLED;
206        }
207
208        // Special case the message waiting indicator messages
209        boolean handled = false;
210        if (sms.isMWISetMessage()) {
211            mPhone.setVoiceMessageWaiting(1, -1);  // line 1: unknown number of msgs waiting
212            handled = sms.isMwiDontStore();
213            if (VDBG) {
214                Rlog.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
215            }
216        } else if (sms.isMWIClearMessage()) {
217            mPhone.setVoiceMessageWaiting(1, 0);   // line 1: no msgs waiting
218            handled = sms.isMwiDontStore();
219            if (VDBG) {
220                Rlog.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
221            }
222        }
223
224        if (handled) {
225            return Intents.RESULT_SMS_HANDLED;
226        }
227
228        if (!mStorageMonitor.isStorageAvailable() &&
229                sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
230            // It's a storable message and there's no storage available.  Bail.
231            // (See TS 23.038 for a description of class 0 messages.)
232            return Intents.RESULT_SMS_OUT_OF_MEMORY;
233        }
234
235        return dispatchNormalMessage(smsb);
236    }
237
238    /** {@inheritDoc} */
239    @Override
240    protected void sendData(String destAddr, String scAddr, int destPort,
241            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
242        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
243                scAddr, destAddr, destPort, data, (deliveryIntent != null));
244        if (pdu != null) {
245            sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
246                    destAddr);
247        } else {
248            Rlog.e(TAG, "GsmSMSDispatcher.sendData(): getSubmitPdu() returned null");
249        }
250    }
251
252    /** {@inheritDoc} */
253    @Override
254    protected void sendText(String destAddr, String scAddr, String text,
255            PendingIntent sentIntent, PendingIntent deliveryIntent) {
256        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
257                scAddr, destAddr, text, (deliveryIntent != null));
258        if (pdu != null) {
259            sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
260                    destAddr);
261        } else {
262            Rlog.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null");
263        }
264    }
265
266    /** {@inheritDoc} */
267    @Override
268    protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
269            boolean use7bitOnly) {
270        return SmsMessage.calculateLength(messageBody, use7bitOnly);
271    }
272
273    /** {@inheritDoc} */
274    @Override
275    protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
276            String message, SmsHeader smsHeader, int encoding,
277            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
278        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
279                message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
280                encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
281        if (pdu != null) {
282            sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
283                    destinationAddress);
284        } else {
285            Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
286        }
287    }
288
289    /** {@inheritDoc} */
290    @Override
291    protected void sendSms(SmsTracker tracker) {
292        HashMap<String, Object> map = tracker.mData;
293
294        byte smsc[] = (byte[]) map.get("smsc");
295        byte pdu[] = (byte[]) map.get("pdu");
296
297        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
298        mCm.sendSMS(IccUtils.bytesToHexString(smsc), IccUtils.bytesToHexString(pdu), reply);
299    }
300
301    /** {@inheritDoc} */
302    @Override
303    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
304        mCm.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
305    }
306
307    private static int resultToCause(int rc) {
308        switch (rc) {
309            case Activity.RESULT_OK:
310            case Intents.RESULT_SMS_HANDLED:
311                // Cause code is ignored on success.
312                return 0;
313            case Intents.RESULT_SMS_OUT_OF_MEMORY:
314                return CommandsInterface.GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED;
315            case Intents.RESULT_SMS_GENERIC_ERROR:
316            default:
317                return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR;
318        }
319    }
320
321    /**
322     * Holds all info about a message page needed to assemble a complete
323     * concatenated message
324     */
325    private static final class SmsCbConcatInfo {
326
327        private final SmsCbHeader mHeader;
328        private final SmsCbLocation mLocation;
329
330        public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
331            mHeader = header;
332            mLocation = location;
333        }
334
335        @Override
336        public int hashCode() {
337            return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
338        }
339
340        @Override
341        public boolean equals(Object obj) {
342            if (obj instanceof SmsCbConcatInfo) {
343                SmsCbConcatInfo other = (SmsCbConcatInfo)obj;
344
345                // Two pages match if they have the same serial number (which includes the
346                // geographical scope and update number), and both pages belong to the same
347                // location (PLMN, plus LAC and CID if these are part of the geographical scope).
348                return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
349                        && mLocation.equals(other.mLocation);
350            }
351
352            return false;
353        }
354
355        /**
356         * Compare the location code for this message to the current location code. The match is
357         * relative to the geographical scope of the message, which determines whether the LAC
358         * and Cell ID are saved in mLocation or set to -1 to match all values.
359         *
360         * @param plmn the current PLMN
361         * @param lac the current Location Area (GSM) or Service Area (UMTS)
362         * @param cid the current Cell ID
363         * @return true if this message is valid for the current location; false otherwise
364         */
365        public boolean matchesLocation(String plmn, int lac, int cid) {
366            return mLocation.isInLocationArea(plmn, lac, cid);
367        }
368    }
369
370    // This map holds incomplete concatenated messages waiting for assembly
371    private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
372            new HashMap<SmsCbConcatInfo, byte[][]>();
373
374    /**
375     * Handle 3GPP format SMS-CB message.
376     * @param ar the AsyncResult containing the received PDUs
377     */
378    private void handleBroadcastSms(AsyncResult ar) {
379        try {
380            byte[] receivedPdu = (byte[])ar.result;
381
382            if (VDBG) {
383                for (int i = 0; i < receivedPdu.length; i += 8) {
384                    StringBuilder sb = new StringBuilder("SMS CB pdu data: ");
385                    for (int j = i; j < i + 8 && j < receivedPdu.length; j++) {
386                        int b = receivedPdu[j] & 0xff;
387                        if (b < 0x10) {
388                            sb.append('0');
389                        }
390                        sb.append(Integer.toHexString(b)).append(' ');
391                    }
392                    Rlog.d(TAG, sb.toString());
393                }
394            }
395
396            SmsCbHeader header = new SmsCbHeader(receivedPdu);
397            String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
398            int lac = -1;
399            int cid = -1;
400            android.telephony.CellLocation cl = mPhone.getCellLocation();
401            // Check if cell location is GsmCellLocation.  This is required to support
402            // dual-mode devices such as CDMA/LTE devices that require support for
403            // both 3GPP and 3GPP2 format messages
404            if (cl instanceof GsmCellLocation) {
405                GsmCellLocation cellLocation = (GsmCellLocation)cl;
406                lac = cellLocation.getLac();
407                cid = cellLocation.getCid();
408            }
409
410            SmsCbLocation location;
411            switch (header.getGeographicalScope()) {
412                case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE:
413                    location = new SmsCbLocation(plmn, lac, -1);
414                    break;
415
416                case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
417                case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
418                    location = new SmsCbLocation(plmn, lac, cid);
419                    break;
420
421                case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
422                default:
423                    location = new SmsCbLocation(plmn);
424                    break;
425            }
426
427            byte[][] pdus;
428            int pageCount = header.getNumberOfPages();
429            if (pageCount > 1) {
430                // Multi-page message
431                SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
432
433                // Try to find other pages of the same message
434                pdus = mSmsCbPageMap.get(concatInfo);
435
436                if (pdus == null) {
437                    // This is the first page of this message, make room for all
438                    // pages and keep until complete
439                    pdus = new byte[pageCount][];
440
441                    mSmsCbPageMap.put(concatInfo, pdus);
442                }
443
444                // Page parameter is one-based
445                pdus[header.getPageIndex() - 1] = receivedPdu;
446
447                for (int i = 0; i < pdus.length; i++) {
448                    if (pdus[i] == null) {
449                        // Still missing pages, exit
450                        return;
451                    }
452                }
453
454                // Message complete, remove and dispatch
455                mSmsCbPageMap.remove(concatInfo);
456            } else {
457                // Single page message
458                pdus = new byte[1][];
459                pdus[0] = receivedPdu;
460            }
461
462            SmsCbMessage message = GsmSmsCbMessage.createSmsCbMessage(header, location, pdus);
463            dispatchBroadcastMessage(message);
464
465            // Remove messages that are out of scope to prevent the map from
466            // growing indefinitely, containing incomplete messages that were
467            // never assembled
468            Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator();
469
470            while (iter.hasNext()) {
471                SmsCbConcatInfo info = iter.next();
472
473                if (!info.matchesLocation(plmn, lac, cid)) {
474                    iter.remove();
475                }
476            }
477        } catch (RuntimeException e) {
478            Rlog.e(TAG, "Error in decoding SMS CB pdu", e);
479        }
480    }
481}
482