CdmaSMSDispatcher.java revision c38bb60d867c5d61d90b7179a9ed2b2d1848124f
1/*
2 * Copyright (C) 2008 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.cdma;
18
19
20import android.app.Activity;
21import android.app.PendingIntent;
22import android.app.PendingIntent.CanceledException;
23import android.content.ContentValues;
24import android.content.Intent;
25import android.content.SharedPreferences;
26import android.database.Cursor;
27import android.database.SQLException;
28import android.os.Message;
29import android.os.SystemProperties;
30import android.preference.PreferenceManager;
31import android.provider.Telephony;
32import android.provider.Telephony.Sms.Intents;
33import android.telephony.SmsCbMessage;
34import android.telephony.SmsManager;
35import android.telephony.cdma.CdmaSmsCbProgramData;
36import android.util.Log;
37
38import com.android.internal.telephony.CommandsInterface;
39import com.android.internal.telephony.GsmAlphabet;
40import com.android.internal.telephony.SmsConstants;
41import com.android.internal.telephony.SMSDispatcher;
42import com.android.internal.telephony.SmsHeader;
43import com.android.internal.telephony.SmsMessageBase;
44import com.android.internal.telephony.SmsStorageMonitor;
45import com.android.internal.telephony.SmsUsageMonitor;
46import com.android.internal.telephony.TelephonyProperties;
47import com.android.internal.telephony.WspTypeDecoder;
48import com.android.internal.telephony.cdma.sms.SmsEnvelope;
49import com.android.internal.telephony.cdma.sms.UserData;
50import com.android.internal.util.HexDump;
51
52import java.io.ByteArrayOutputStream;
53import java.util.Arrays;
54import java.util.HashMap;
55import java.util.List;
56
57import android.content.res.Resources;
58
59
60final class CdmaSMSDispatcher extends SMSDispatcher {
61    private static final String TAG = "CDMA";
62
63    private byte[] mLastDispatchedSmsFingerprint;
64    private byte[] mLastAcknowledgedSmsFingerprint;
65
66    private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
67            com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
68
69    CdmaSMSDispatcher(CDMAPhone phone, SmsStorageMonitor storageMonitor,
70            SmsUsageMonitor usageMonitor) {
71        super(phone, storageMonitor, usageMonitor);
72        mCm.setOnNewCdmaSms(this, EVENT_NEW_SMS, null);
73    }
74
75    @Override
76    public void dispose() {
77        mCm.unSetOnNewCdmaSms(this);
78    }
79
80    @Override
81    protected String getFormat() {
82        return android.telephony.SmsMessage.FORMAT_3GPP2;
83    }
84
85    private void handleCdmaStatusReport(SmsMessage sms) {
86        for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
87            SmsTracker tracker = deliveryPendingList.get(i);
88            if (tracker.mMessageRef == sms.messageRef) {
89                // Found it.  Remove from list and broadcast.
90                deliveryPendingList.remove(i);
91                PendingIntent intent = tracker.mDeliveryIntent;
92                Intent fillIn = new Intent();
93                fillIn.putExtra("pdu", sms.getPdu());
94                fillIn.putExtra("format", android.telephony.SmsMessage.FORMAT_3GPP2);
95                try {
96                    intent.send(mContext, Activity.RESULT_OK, fillIn);
97                } catch (CanceledException ex) {}
98                break;  // Only expect to see one tracker matching this message.
99            }
100        }
101    }
102
103    /**
104     * Dispatch service category program data to the CellBroadcastReceiver app, which filters
105     * the broadcast alerts to display.
106     * @param sms the SMS message containing one or more
107     * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects.
108     */
109    private void handleServiceCategoryProgramData(SmsMessage sms) {
110        List<CdmaSmsCbProgramData> programDataList = sms.getSmsCbProgramData();
111        if (programDataList == null) {
112            Log.e(TAG, "handleServiceCategoryProgramData: program data list is null!");
113            return;
114        }
115
116        Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION);
117        intent.putExtra("program_data_list", (CdmaSmsCbProgramData[]) programDataList.toArray());
118        dispatch(intent, RECEIVE_SMS_PERMISSION);
119    }
120
121    /** {@inheritDoc} */
122    @Override
123    public int dispatchMessage(SmsMessageBase smsb) {
124
125        // If sms is null, means there was a parsing error.
126        if (smsb == null) {
127            Log.e(TAG, "dispatchMessage: message is null");
128            return Intents.RESULT_SMS_GENERIC_ERROR;
129        }
130
131        String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
132        if (inEcm.equals("true")) {
133            return Activity.RESULT_OK;
134        }
135
136        if (mSmsReceiveDisabled) {
137            // Device doesn't support receiving SMS,
138            Log.d(TAG, "Received short message on device which doesn't support "
139                    + "receiving SMS. Ignored.");
140            return Intents.RESULT_SMS_HANDLED;
141        }
142
143        SmsMessage sms = (SmsMessage) smsb;
144
145        // Handle CMAS emergency broadcast messages.
146        if (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()) {
147            Log.d(TAG, "Broadcast type message");
148            SmsCbMessage message = sms.parseBroadcastSms();
149            if (message != null) {
150                dispatchBroadcastMessage(message);
151            }
152            return Intents.RESULT_SMS_HANDLED;
153        }
154
155        // See if we have a network duplicate SMS.
156        mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
157        if (mLastAcknowledgedSmsFingerprint != null &&
158                Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
159            return Intents.RESULT_SMS_HANDLED;
160        }
161        // Decode BD stream and set sms variables.
162        sms.parseSms();
163        int teleService = sms.getTeleService();
164        boolean handled = false;
165
166        if ((SmsEnvelope.TELESERVICE_VMN == teleService) ||
167                (SmsEnvelope.TELESERVICE_MWI == teleService)) {
168            // handling Voicemail
169            int voicemailCount = sms.getNumOfVoicemails();
170            Log.d(TAG, "Voicemail count=" + voicemailCount);
171            // Store the voicemail count in preferences.
172            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
173                    mContext);
174            SharedPreferences.Editor editor = sp.edit();
175            editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
176            editor.apply();
177            mPhone.setVoiceMessageWaiting(1, voicemailCount);
178            handled = true;
179        } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) ||
180                (SmsEnvelope.TELESERVICE_WEMT == teleService)) &&
181                sms.isStatusReportMessage()) {
182            handleCdmaStatusReport(sms);
183            handled = true;
184        } else if (SmsEnvelope.TELESERVICE_SCPT == teleService) {
185            handleServiceCategoryProgramData(sms);
186            handled = true;
187        } else if ((sms.getUserData() == null)) {
188            if (false) {
189                Log.d(TAG, "Received SMS without user data");
190            }
191            handled = true;
192        }
193
194        if (handled) {
195            return Intents.RESULT_SMS_HANDLED;
196        }
197
198        if (!mStorageMonitor.isStorageAvailable() &&
199                sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
200            // It's a storable message and there's no storage available.  Bail.
201            // (See C.S0015-B v2.0 for a description of "Immediate Display"
202            // messages, which we represent as CLASS_0.)
203            return Intents.RESULT_SMS_OUT_OF_MEMORY;
204        }
205
206        if (SmsEnvelope.TELESERVICE_WAP == teleService) {
207            return processCdmaWapPdu(sms.getUserData(), sms.messageRef,
208                    sms.getOriginatingAddress());
209        }
210
211        // Reject (NAK) any messages with teleservice ids that have
212        // not yet been handled and also do not correspond to the two
213        // kinds that are processed below.
214        if ((SmsEnvelope.TELESERVICE_WMT != teleService) &&
215                (SmsEnvelope.TELESERVICE_WEMT != teleService) &&
216                (SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) {
217            return Intents.RESULT_SMS_UNSUPPORTED;
218        }
219
220        return dispatchNormalMessage(smsb);
221    }
222
223    /**
224     * Processes inbound messages that are in the WAP-WDP PDU format. See
225     * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
226     * WDP segments are gathered until a datagram completes and gets dispatched.
227     *
228     * @param pdu The WAP-WDP PDU segment
229     * @return a result code from {@link Telephony.Sms.Intents}, or
230     *         {@link Activity#RESULT_OK} if the message has been broadcast
231     *         to applications
232     */
233    protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) {
234        int index = 0;
235
236        int msgType = (0xFF & pdu[index++]);
237        if (msgType != 0) {
238            Log.w(TAG, "Received a WAP SMS which is not WDP. Discard.");
239            return Intents.RESULT_SMS_HANDLED;
240        }
241        int totalSegments = (0xFF & pdu[index++]);   // >= 1
242        int segment = (0xFF & pdu[index++]);         // >= 0
243
244        if (segment >= totalSegments) {
245            Log.e(TAG, "WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1));
246            return Intents.RESULT_SMS_HANDLED;
247        }
248
249        // Only the first segment contains sourcePort and destination Port
250        int sourcePort = 0;
251        int destinationPort = 0;
252        if (segment == 0) {
253            //process WDP segment
254            sourcePort = (0xFF & pdu[index++]) << 8;
255            sourcePort |= 0xFF & pdu[index++];
256            destinationPort = (0xFF & pdu[index++]) << 8;
257            destinationPort |= 0xFF & pdu[index++];
258            // Some carriers incorrectly send duplicate port fields in omadm wap pushes.
259            // If configured, check for that here
260            if (mCheckForDuplicatePortsInOmadmWapPush) {
261                if (checkDuplicatePortOmadmWappush(pdu,index)) {
262                    index = index + 4; // skip duplicate port fields
263                }
264            }
265        }
266
267        // Lookup all other related parts
268        Log.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address
269                + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
270                + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments);
271
272        // pass the user data portion of the PDU to the shared handler in SMSDispatcher
273        byte[] userData = new byte[pdu.length - index];
274        System.arraycopy(pdu, index, userData, 0, pdu.length - index);
275
276        return processMessagePart(userData, address, referenceNumber, segment, totalSegments,
277                0L, destinationPort, true);
278    }
279
280    /** {@inheritDoc} */
281    @Override
282    protected void sendData(String destAddr, String scAddr, int destPort,
283            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
284        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
285                scAddr, destAddr, destPort, data, (deliveryIntent != null));
286        sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
287    }
288
289    /** {@inheritDoc} */
290    @Override
291    protected void sendText(String destAddr, String scAddr, String text,
292            PendingIntent sentIntent, PendingIntent deliveryIntent) {
293        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
294                scAddr, destAddr, text, (deliveryIntent != null), null);
295        sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
296    }
297
298    /** {@inheritDoc} */
299    @Override
300    protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
301            boolean use7bitOnly) {
302        return SmsMessage.calculateLength(messageBody, use7bitOnly);
303    }
304
305    /** {@inheritDoc} */
306    @Override
307    protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
308            String message, SmsHeader smsHeader, int encoding,
309            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
310        UserData uData = new UserData();
311        uData.payloadStr = message;
312        uData.userDataHeader = smsHeader;
313        if (encoding == SmsConstants.ENCODING_7BIT) {
314            uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
315        } else { // assume UTF-16
316            uData.msgEncoding = UserData.ENCODING_UNICODE_16;
317        }
318        uData.msgEncodingSet = true;
319
320        /* By setting the statusReportRequested bit only for the
321         * last message fragment, this will result in only one
322         * callback to the sender when that last fragment delivery
323         * has been acknowledged. */
324        SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
325                uData, (deliveryIntent != null) && lastPart);
326
327        sendSubmitPdu(submitPdu, sentIntent, deliveryIntent, destinationAddress);
328    }
329
330    protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
331            PendingIntent sentIntent, PendingIntent deliveryIntent, String destAddr) {
332        if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
333            if (sentIntent != null) {
334                try {
335                    sentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
336                } catch (CanceledException ex) {}
337            }
338            if (false) {
339                Log.d(TAG, "Block SMS in Emergency Callback mode");
340            }
341            return;
342        }
343        sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, destAddr);
344    }
345
346    /** {@inheritDoc} */
347    @Override
348    protected void sendSms(SmsTracker tracker) {
349        HashMap<String, Object> map = tracker.mData;
350
351        // byte smsc[] = (byte[]) map.get("smsc");  // unused for CDMA
352        byte pdu[] = (byte[]) map.get("pdu");
353
354        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
355        mCm.sendCdmaSms(pdu, reply);
356    }
357
358    /** {@inheritDoc} */
359    @Override
360    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
361        String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
362        if (inEcm.equals("true")) {
363            return;
364        }
365
366        int causeCode = resultToCause(result);
367        mCm.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
368
369        if (causeCode == 0) {
370            mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
371        }
372        mLastDispatchedSmsFingerprint = null;
373    }
374
375    private static int resultToCause(int rc) {
376        switch (rc) {
377        case Activity.RESULT_OK:
378        case Intents.RESULT_SMS_HANDLED:
379            // Cause code is ignored on success.
380            return 0;
381        case Intents.RESULT_SMS_OUT_OF_MEMORY:
382            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
383        case Intents.RESULT_SMS_UNSUPPORTED:
384            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
385        case Intents.RESULT_SMS_GENERIC_ERROR:
386        default:
387            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
388        }
389    }
390
391    /**
392     * Optional check to see if the received WapPush is an OMADM notification with erroneous
393     * extra port fields.
394     * - Some carriers make this mistake.
395     * ex: MSGTYPE-TotalSegments-CurrentSegment
396     *       -SourcePortDestPort-SourcePortDestPort-OMADM PDU
397     * @param origPdu The WAP-WDP PDU segment
398     * @param index Current Index while parsing the PDU.
399     * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
400     *         False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
401     */
402    private static boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) {
403        index += 4;
404        byte[] omaPdu = new byte[origPdu.length - index];
405        System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
406
407        WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu);
408        int wspIndex = 2;
409
410        // Process header length field
411        if (pduDecoder.decodeUintvarInteger(wspIndex) == false) {
412            return false;
413        }
414
415        wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field
416
417        // Process content type field
418        if (pduDecoder.decodeContentType(wspIndex) == false) {
419            return false;
420        }
421
422        String mimeType = pduDecoder.getValueString();
423        if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI)) {
424            return true;
425        }
426        return false;
427    }
428}
429