CdmaSMSDispatcher.java revision 850665a367489cce0b83431fa0e6e543b24062e0
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 * Copyright (c) 2012, The Linux Foundation. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.internal.telephony.cdma;
19
20
21import android.app.Activity;
22import android.app.AppOpsManager;
23import android.app.PendingIntent;
24import android.app.PendingIntent.CanceledException;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.SharedPreferences;
29import android.content.res.Resources;
30import android.os.Bundle;
31import android.os.Message;
32import android.os.SystemProperties;
33import android.preference.PreferenceManager;
34import android.provider.Telephony.Sms.Intents;
35import android.telephony.PhoneNumberUtils;
36import android.telephony.SmsCbMessage;
37import android.telephony.SmsManager;
38import android.telephony.cdma.CdmaSmsCbProgramData;
39import android.telephony.cdma.CdmaSmsCbProgramResults;
40import android.telephony.Rlog;
41
42import com.android.internal.telephony.CommandsInterface;
43import com.android.internal.telephony.GsmAlphabet;
44import com.android.internal.telephony.SmsConstants;
45import com.android.internal.telephony.PhoneBase;
46import com.android.internal.telephony.SMSDispatcher;
47import com.android.internal.telephony.ImsSMSDispatcher;
48import com.android.internal.telephony.SmsHeader;
49import com.android.internal.telephony.SmsMessageBase;
50import com.android.internal.telephony.SmsStorageMonitor;
51import com.android.internal.telephony.SmsUsageMonitor;
52import com.android.internal.telephony.TelephonyProperties;
53import com.android.internal.telephony.WspTypeDecoder;
54import com.android.internal.telephony.cdma.sms.BearerData;
55import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
56import com.android.internal.telephony.cdma.sms.SmsEnvelope;
57import com.android.internal.telephony.cdma.sms.UserData;
58
59import java.io.ByteArrayOutputStream;
60import java.io.DataOutputStream;
61import java.io.IOException;
62import java.util.ArrayList;
63import java.util.Arrays;
64import java.util.HashMap;
65
66
67public class CdmaSMSDispatcher extends SMSDispatcher {
68    private static final String TAG = "CdmaSMSDispatcher";
69    private static final boolean VDBG = false;
70    private ImsSMSDispatcher mImsSMSDispatcher;
71
72    private byte[] mLastDispatchedSmsFingerprint;
73    private byte[] mLastAcknowledgedSmsFingerprint;
74
75    private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
76            com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
77
78    public CdmaSMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor,
79            SmsUsageMonitor usageMonitor, ImsSMSDispatcher imsSMSDispatcher) {
80        super(phone, storageMonitor, usageMonitor);
81        mCi.setOnNewCdmaSms(this, EVENT_NEW_SMS, null);
82        mImsSMSDispatcher = imsSMSDispatcher;
83        Rlog.d(TAG, "CdmaSMSDispatcher created");
84    }
85
86    @Override
87    public void dispose() {
88        mCi.unSetOnNewCdmaSms(this);
89    }
90
91    @Override
92    protected String getFormat() {
93        return SmsConstants.FORMAT_3GPP2;
94    }
95
96    private void handleCdmaStatusReport(SmsMessage sms) {
97        for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
98            SmsTracker tracker = deliveryPendingList.get(i);
99            if (tracker.mMessageRef == sms.mMessageRef) {
100                // Found it.  Remove from list and broadcast.
101                deliveryPendingList.remove(i);
102                PendingIntent intent = tracker.mDeliveryIntent;
103                Intent fillIn = new Intent();
104                fillIn.putExtra("pdu", sms.getPdu());
105                fillIn.putExtra("format", getFormat());
106                try {
107                    intent.send(mContext, Activity.RESULT_OK, fillIn);
108                } catch (CanceledException ex) {}
109                break;  // Only expect to see one tracker matching this message.
110            }
111        }
112    }
113
114    /**
115     * Dispatch service category program data to the CellBroadcastReceiver app, which filters
116     * the broadcast alerts to display.
117     * @param sms the SMS message containing one or more
118     * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects.
119     */
120    private void handleServiceCategoryProgramData(SmsMessage sms) {
121        ArrayList<CdmaSmsCbProgramData> programDataList = sms.getSmsCbProgramData();
122        if (programDataList == null) {
123            Rlog.e(TAG, "handleServiceCategoryProgramData: program data list is null!");
124            return;
125        }
126
127        Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION);
128        intent.putExtra("sender", sms.getOriginatingAddress());
129        intent.putParcelableArrayListExtra("program_data", programDataList);
130        dispatch(intent, RECEIVE_SMS_PERMISSION, AppOpsManager.OP_RECEIVE_SMS, mScpResultsReceiver);
131    }
132
133    /** {@inheritDoc} */
134    @Override
135    protected int dispatchMessage(SmsMessageBase smsb) {
136
137        // If sms is null, means there was a parsing error.
138        if (smsb == null) {
139            Rlog.e(TAG, "dispatchMessage: message is null");
140            return Intents.RESULT_SMS_GENERIC_ERROR;
141        }
142
143        String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
144        if (inEcm.equals("true")) {
145            return Activity.RESULT_OK;
146        }
147
148        if (mSmsReceiveDisabled) {
149            // Device doesn't support receiving SMS,
150            Rlog.d(TAG, "Received short message on device which doesn't support "
151                    + "receiving SMS. Ignored.");
152            return Intents.RESULT_SMS_HANDLED;
153        }
154
155        SmsMessage sms = (SmsMessage) smsb;
156
157        // Handle CMAS emergency broadcast messages.
158        if (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()) {
159            Rlog.d(TAG, "Broadcast type message");
160            SmsCbMessage message = sms.parseBroadcastSms();
161            if (message != null) {
162                dispatchBroadcastMessage(message);
163            }
164            return Intents.RESULT_SMS_HANDLED;
165        }
166
167        // See if we have a network duplicate SMS.
168        mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
169        if (mLastAcknowledgedSmsFingerprint != null &&
170                Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
171            return Intents.RESULT_SMS_HANDLED;
172        }
173        // Decode BD stream and set sms variables.
174        sms.parseSms();
175        int teleService = sms.getTeleService();
176        boolean handled = false;
177
178        if ((SmsEnvelope.TELESERVICE_VMN == teleService) ||
179                (SmsEnvelope.TELESERVICE_MWI == teleService)) {
180            // handling Voicemail
181            int voicemailCount = sms.getNumOfVoicemails();
182            Rlog.d(TAG, "Voicemail count=" + voicemailCount);
183            // Store the voicemail count in preferences.
184            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
185                    mContext);
186            SharedPreferences.Editor editor = sp.edit();
187            editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
188            editor.apply();
189            mPhone.setVoiceMessageWaiting(1, voicemailCount);
190            handled = true;
191        } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) ||
192                (SmsEnvelope.TELESERVICE_WEMT == teleService)) &&
193                sms.isStatusReportMessage()) {
194            handleCdmaStatusReport(sms);
195            handled = true;
196        } else if (SmsEnvelope.TELESERVICE_SCPT == teleService) {
197            handleServiceCategoryProgramData(sms);
198            handled = true;
199        } else if ((sms.getUserData() == null)) {
200            if (VDBG) {
201                Rlog.d(TAG, "Received SMS without user data");
202            }
203            handled = true;
204        }
205
206        if (handled) {
207            return Intents.RESULT_SMS_HANDLED;
208        }
209
210        if (!mStorageMonitor.isStorageAvailable() &&
211                sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
212            // It's a storable message and there's no storage available.  Bail.
213            // (See C.S0015-B v2.0 for a description of "Immediate Display"
214            // messages, which we represent as CLASS_0.)
215            return Intents.RESULT_SMS_OUT_OF_MEMORY;
216        }
217
218        if (SmsEnvelope.TELESERVICE_WAP == teleService) {
219            return processCdmaWapPdu(sms.getUserData(), sms.mMessageRef,
220                    sms.getOriginatingAddress());
221        }
222
223        // Reject (NAK) any messages with teleservice ids that have
224        // not yet been handled and also do not correspond to the two
225        // kinds that are processed below.
226        if ((SmsEnvelope.TELESERVICE_WMT != teleService) &&
227                (SmsEnvelope.TELESERVICE_WEMT != teleService) &&
228                (SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) {
229            return Intents.RESULT_SMS_UNSUPPORTED;
230        }
231
232        return dispatchNormalMessage(smsb);
233    }
234
235    /**
236     * Processes inbound messages that are in the WAP-WDP PDU format. See
237     * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
238     * WDP segments are gathered until a datagram completes and gets dispatched.
239     *
240     * @param pdu The WAP-WDP PDU segment
241     * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
242     *         {@link Activity#RESULT_OK} if the message has been broadcast
243     *         to applications
244     */
245    protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) {
246        int index = 0;
247
248        int msgType = (0xFF & pdu[index++]);
249        if (msgType != 0) {
250            Rlog.w(TAG, "Received a WAP SMS which is not WDP. Discard.");
251            return Intents.RESULT_SMS_HANDLED;
252        }
253        int totalSegments = (0xFF & pdu[index++]);   // >= 1
254        int segment = (0xFF & pdu[index++]);         // >= 0
255
256        if (segment >= totalSegments) {
257            Rlog.e(TAG, "WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1));
258            return Intents.RESULT_SMS_HANDLED;
259        }
260
261        // Only the first segment contains sourcePort and destination Port
262        int sourcePort = 0;
263        int destinationPort = 0;
264        if (segment == 0) {
265            //process WDP segment
266            sourcePort = (0xFF & pdu[index++]) << 8;
267            sourcePort |= 0xFF & pdu[index++];
268            destinationPort = (0xFF & pdu[index++]) << 8;
269            destinationPort |= 0xFF & pdu[index++];
270            // Some carriers incorrectly send duplicate port fields in omadm wap pushes.
271            // If configured, check for that here
272            if (mCheckForDuplicatePortsInOmadmWapPush) {
273                if (checkDuplicatePortOmadmWappush(pdu,index)) {
274                    index = index + 4; // skip duplicate port fields
275                }
276            }
277        }
278
279        // Lookup all other related parts
280        Rlog.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address
281                + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
282                + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments);
283
284        // pass the user data portion of the PDU to the shared handler in SMSDispatcher
285        byte[] userData = new byte[pdu.length - index];
286        System.arraycopy(pdu, index, userData, 0, pdu.length - index);
287
288        return processMessagePart(userData, address, referenceNumber, segment, totalSegments,
289                0L, destinationPort, true);
290    }
291
292    /** {@inheritDoc} */
293    @Override
294    protected void sendData(String destAddr, String scAddr, int destPort,
295            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
296        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
297                scAddr, destAddr, destPort, data, (deliveryIntent != null));
298        HashMap map =  SmsTrackerMapFactory(destAddr, scAddr, destPort, data, pdu);
299        SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent,
300                getFormat());
301        sendSubmitPdu(tracker);
302    }
303
304    /** {@inheritDoc} */
305    @Override
306    protected void sendText(String destAddr, String scAddr, String text,
307            PendingIntent sentIntent, PendingIntent deliveryIntent) {
308        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
309                scAddr, destAddr, text, (deliveryIntent != null), null);
310        HashMap map =  SmsTrackerMapFactory(destAddr, scAddr, text, pdu);
311        SmsTracker tracker = SmsTrackerFactory(map, sentIntent,
312                deliveryIntent, getFormat());
313        sendSubmitPdu(tracker);
314    }
315
316    /** {@inheritDoc} */
317    @Override
318    protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
319            boolean use7bitOnly) {
320        return SmsMessage.calculateLength(messageBody, use7bitOnly);
321    }
322
323    /** {@inheritDoc} */
324    @Override
325    protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
326            String message, SmsHeader smsHeader, int encoding,
327            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
328        UserData uData = new UserData();
329        uData.payloadStr = message;
330        uData.userDataHeader = smsHeader;
331        if (encoding == SmsConstants.ENCODING_7BIT) {
332            uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
333        } else { // assume UTF-16
334            uData.msgEncoding = UserData.ENCODING_UNICODE_16;
335        }
336        uData.msgEncodingSet = true;
337
338        /* By setting the statusReportRequested bit only for the
339         * last message fragment, this will result in only one
340         * callback to the sender when that last fragment delivery
341         * has been acknowledged. */
342        SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
343                uData, (deliveryIntent != null) && lastPart);
344
345        HashMap map =  SmsTrackerMapFactory(destinationAddress, scAddress,
346                message, submitPdu);
347        SmsTracker tracker = SmsTrackerFactory(map, sentIntent,
348                deliveryIntent, getFormat());
349        sendSubmitPdu(tracker);
350    }
351
352    protected void sendSubmitPdu(SmsTracker tracker) {
353        if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
354            if (tracker.mSentIntent != null) {
355                try {
356                    tracker.mSentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
357                } catch (CanceledException ex) {}
358            }
359            if (VDBG) {
360                Rlog.d(TAG, "Block SMS in Emergency Callback mode");
361            }
362            return;
363        }
364        sendRawPdu(tracker);
365    }
366
367    /** {@inheritDoc} */
368    @Override
369    protected void sendSms(SmsTracker tracker) {
370        HashMap<String, Object> map = tracker.mData;
371
372        // byte smsc[] = (byte[]) map.get("smsc");  // unused for CDMA
373        byte pdu[] = (byte[]) map.get("pdu");
374
375        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
376
377        Rlog.d(TAG, "sendSms: "
378                +" isIms()="+isIms()
379                +" mRetryCount="+tracker.mRetryCount
380                +" mImsRetry="+tracker.mImsRetry
381                +" mMessageRef="+tracker.mMessageRef
382                +" SS=" +mPhone.getServiceState().getState());
383
384        // sms over cdma is used:
385        //   if sms over IMS is not supported AND
386        //   this is not a retry case after sms over IMS failed
387        //     indicated by mImsRetry > 0
388        if (0 == tracker.mImsRetry && !isIms()) {
389            mCi.sendCdmaSms(pdu, reply);
390        } else {
391            mCi.sendImsCdmaSms(pdu, tracker.mImsRetry, tracker.mMessageRef, reply);
392            // increment it here, so in case of SMS_FAIL_RETRY over IMS
393            // next retry will be sent using IMS request again.
394            tracker.mImsRetry++;
395        }
396    }
397
398    @Override
399    public void sendRetrySms(SmsTracker tracker) {
400        //re-routing to ImsSMSDispatcher
401        mImsSMSDispatcher.sendRetrySms(tracker);
402    }
403
404    /** {@inheritDoc} */
405    @Override
406    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
407        String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
408        if (inEcm.equals("true")) {
409            return;
410        }
411
412        int causeCode = resultToCause(result);
413        mCi.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
414
415        if (causeCode == 0) {
416            mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
417        }
418        mLastDispatchedSmsFingerprint = null;
419    }
420
421    private static int resultToCause(int rc) {
422        switch (rc) {
423        case Activity.RESULT_OK:
424        case Intents.RESULT_SMS_HANDLED:
425            // Cause code is ignored on success.
426            return 0;
427        case Intents.RESULT_SMS_OUT_OF_MEMORY:
428            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
429        case Intents.RESULT_SMS_UNSUPPORTED:
430            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
431        case Intents.RESULT_SMS_GENERIC_ERROR:
432        default:
433            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
434        }
435    }
436
437    /**
438     * Optional check to see if the received WapPush is an OMADM notification with erroneous
439     * extra port fields.
440     * - Some carriers make this mistake.
441     * ex: MSGTYPE-TotalSegments-CurrentSegment
442     *       -SourcePortDestPort-SourcePortDestPort-OMADM PDU
443     * @param origPdu The WAP-WDP PDU segment
444     * @param index Current Index while parsing the PDU.
445     * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
446     *         False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
447     */
448    private static boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) {
449        index += 4;
450        byte[] omaPdu = new byte[origPdu.length - index];
451        System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
452
453        WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu);
454        int wspIndex = 2;
455
456        // Process header length field
457        if (pduDecoder.decodeUintvarInteger(wspIndex) == false) {
458            return false;
459        }
460
461        wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field
462
463        // Process content type field
464        if (pduDecoder.decodeContentType(wspIndex) == false) {
465            return false;
466        }
467
468        String mimeType = pduDecoder.getValueString();
469        if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI)) {
470            return true;
471        }
472        return false;
473    }
474
475    // Receiver for Service Category Program Data results.
476    // We already ACK'd the original SCPD SMS, so this sends a new response SMS.
477    // TODO: handle retries if the RIL returns an error.
478    private final BroadcastReceiver mScpResultsReceiver = new BroadcastReceiver() {
479        @Override
480        public void onReceive(Context context, Intent intent) {
481            int rc = getResultCode();
482            boolean success = (rc == Activity.RESULT_OK) || (rc == Intents.RESULT_SMS_HANDLED);
483            if (!success) {
484                Rlog.e(TAG, "SCP results error: result code = " + rc);
485                return;
486            }
487            Bundle extras = getResultExtras(false);
488            if (extras == null) {
489                Rlog.e(TAG, "SCP results error: missing extras");
490                return;
491            }
492            String sender = extras.getString("sender");
493            if (sender == null) {
494                Rlog.e(TAG, "SCP results error: missing sender extra.");
495                return;
496            }
497            ArrayList<CdmaSmsCbProgramResults> results
498                    = extras.getParcelableArrayList("results");
499            if (results == null) {
500                Rlog.e(TAG, "SCP results error: missing results extra.");
501                return;
502            }
503
504            BearerData bData = new BearerData();
505            bData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
506            bData.messageId = SmsMessage.getNextMessageId();
507            bData.serviceCategoryProgramResults = results;
508            byte[] encodedBearerData = BearerData.encode(bData);
509
510            ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
511            DataOutputStream dos = new DataOutputStream(baos);
512            try {
513                dos.writeInt(SmsEnvelope.TELESERVICE_SCPT);
514                dos.writeInt(0); //servicePresent
515                dos.writeInt(0); //serviceCategory
516                CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
517                        PhoneNumberUtils.cdmaCheckAndProcessPlusCode(sender));
518                dos.write(destAddr.digitMode);
519                dos.write(destAddr.numberMode);
520                dos.write(destAddr.ton); // number_type
521                dos.write(destAddr.numberPlan);
522                dos.write(destAddr.numberOfDigits);
523                dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
524                // Subaddress is not supported.
525                dos.write(0); //subaddressType
526                dos.write(0); //subaddr_odd
527                dos.write(0); //subaddr_nbr_of_digits
528                dos.write(encodedBearerData.length);
529                dos.write(encodedBearerData, 0, encodedBearerData.length);
530                // Ignore the RIL response. TODO: implement retry if SMS send fails.
531                mCi.sendCdmaSms(baos.toByteArray(), null);
532            } catch (IOException e) {
533                Rlog.e(TAG, "exception creating SCP results PDU", e);
534            } finally {
535                try {
536                    dos.close();
537                } catch (IOException ignored) {
538                }
539            }
540        }
541    };
542
543    @Override
544    public boolean isIms() {
545        return mImsSMSDispatcher.isIms();
546    }
547
548    @Override
549    public String getImsSmsFormat() {
550        return mImsSMSDispatcher.getImsSmsFormat();
551    }
552}
553