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