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