1/*
2 * Copyright (C) 2013 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
19import android.app.Activity;
20import android.content.Context;
21import android.content.res.Resources;
22import android.os.Message;
23import android.os.SystemProperties;
24import android.provider.Telephony.Sms.Intents;
25import android.telephony.SmsCbMessage;
26
27import com.android.internal.telephony.CellBroadcastHandler;
28import com.android.internal.telephony.CommandsInterface;
29import com.android.internal.telephony.InboundSmsHandler;
30import com.android.internal.telephony.InboundSmsTracker;
31import com.android.internal.telephony.PhoneBase;
32import com.android.internal.telephony.SmsConstants;
33import com.android.internal.telephony.SmsMessageBase;
34import com.android.internal.telephony.SmsStorageMonitor;
35import com.android.internal.telephony.TelephonyProperties;
36import com.android.internal.telephony.WspTypeDecoder;
37import com.android.internal.telephony.cdma.sms.SmsEnvelope;
38
39import java.util.Arrays;
40
41/**
42 * Subclass of {@link InboundSmsHandler} for 3GPP2 type messages.
43 */
44public class CdmaInboundSmsHandler extends InboundSmsHandler {
45
46    private final CdmaSMSDispatcher mSmsDispatcher;
47    private final CdmaServiceCategoryProgramHandler mServiceCategoryProgramHandler;
48
49    private byte[] mLastDispatchedSmsFingerprint;
50    private byte[] mLastAcknowledgedSmsFingerprint;
51
52    private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
53            com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
54
55    /**
56     * Create a new inbound SMS handler for CDMA.
57     */
58    private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
59            PhoneBase phone, CdmaSMSDispatcher smsDispatcher) {
60        super("CdmaInboundSmsHandler", context, storageMonitor, phone,
61                CellBroadcastHandler.makeCellBroadcastHandler(context, phone));
62        mSmsDispatcher = smsDispatcher;
63        mServiceCategoryProgramHandler = CdmaServiceCategoryProgramHandler.makeScpHandler(context,
64                phone.mCi);
65        phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null);
66    }
67
68    /**
69     * Unregister for CDMA SMS.
70     */
71    @Override
72    protected void onQuitting() {
73        mPhone.mCi.unSetOnNewCdmaSms(getHandler());
74        mCellBroadcastHandler.dispose();
75
76        if (DBG) log("unregistered for 3GPP2 SMS");
77        super.onQuitting();
78    }
79
80    /**
81     * Wait for state machine to enter startup state. We can't send any messages until then.
82     */
83    public static CdmaInboundSmsHandler makeInboundSmsHandler(Context context,
84            SmsStorageMonitor storageMonitor, PhoneBase phone, CdmaSMSDispatcher smsDispatcher) {
85        CdmaInboundSmsHandler handler = new CdmaInboundSmsHandler(context, storageMonitor,
86                phone, smsDispatcher);
87        handler.start();
88        return handler;
89    }
90
91    /**
92     * Return whether the device is in Emergency Call Mode (only for 3GPP2).
93     * @return true if the device is in ECM; false otherwise
94     */
95    private static boolean isInEmergencyCallMode() {
96        String inEcm = SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
97        return "true".equals(inEcm);
98    }
99
100    /**
101     * Return true if this handler is for 3GPP2 messages; false for 3GPP format.
102     * @return true (3GPP2)
103     */
104    @Override
105    protected boolean is3gpp2() {
106        return true;
107    }
108
109    /**
110     * Process Cell Broadcast, Voicemail Notification, and other 3GPP/3GPP2-specific messages.
111     * @param smsb the SmsMessageBase object from the RIL
112     * @return true if the message was handled here; false to continue processing
113     */
114    @Override
115    protected int dispatchMessageRadioSpecific(SmsMessageBase smsb) {
116        if (isInEmergencyCallMode()) {
117            return Activity.RESULT_OK;
118        }
119
120        SmsMessage sms = (SmsMessage) smsb;
121        boolean isBroadcastType = (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType());
122
123        // Handle CMAS emergency broadcast messages.
124        if (isBroadcastType) {
125            log("Broadcast type message");
126            SmsCbMessage cbMessage = sms.parseBroadcastSms();
127            if (cbMessage != null) {
128                mCellBroadcastHandler.dispatchSmsMessage(cbMessage);
129            } else {
130                loge("error trying to parse broadcast SMS");
131            }
132            return Intents.RESULT_SMS_HANDLED;
133        }
134
135        // Initialize fingerprint field, and see if we have a network duplicate SMS.
136        mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
137        if (mLastAcknowledgedSmsFingerprint != null &&
138                Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
139            return Intents.RESULT_SMS_HANDLED;
140        }
141
142        // Decode BD stream and set sms variables.
143        sms.parseSms();
144        int teleService = sms.getTeleService();
145
146        switch (teleService) {
147            case SmsEnvelope.TELESERVICE_VMN:
148            case SmsEnvelope.TELESERVICE_MWI:
149                // handle voicemail indication
150                handleVoicemailTeleservice(sms);
151                return Intents.RESULT_SMS_HANDLED;
152
153            case SmsEnvelope.TELESERVICE_WMT:
154            case SmsEnvelope.TELESERVICE_WEMT:
155                if (sms.isStatusReportMessage()) {
156                    mSmsDispatcher.sendStatusReportMessage(sms);
157                    return Intents.RESULT_SMS_HANDLED;
158                }
159                break;
160
161            case SmsEnvelope.TELESERVICE_SCPT:
162                mServiceCategoryProgramHandler.dispatchSmsMessage(sms);
163                return Intents.RESULT_SMS_HANDLED;
164
165            case SmsEnvelope.TELESERVICE_WAP:
166                // handled below, after storage check
167                break;
168
169            default:
170                loge("unsupported teleservice 0x" + Integer.toHexString(teleService));
171                return Intents.RESULT_SMS_UNSUPPORTED;
172        }
173
174        if (!mStorageMonitor.isStorageAvailable() &&
175                sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
176            // It's a storable message and there's no storage available.  Bail.
177            // (See C.S0015-B v2.0 for a description of "Immediate Display"
178            // messages, which we represent as CLASS_0.)
179            return Intents.RESULT_SMS_OUT_OF_MEMORY;
180        }
181
182        if (SmsEnvelope.TELESERVICE_WAP == teleService) {
183            return processCdmaWapPdu(sms.getUserData(), sms.mMessageRef,
184                    sms.getOriginatingAddress(), sms.getTimestampMillis());
185        }
186
187        return dispatchNormalMessage(smsb);
188    }
189
190    /**
191     * Send an acknowledge message.
192     * @param success indicates that last message was successfully received.
193     * @param result result code indicating any error
194     * @param response callback message sent when operation completes.
195     */
196    @Override
197    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
198        if (isInEmergencyCallMode()) {
199            return;
200        }
201
202        int causeCode = resultToCause(result);
203        mPhone.mCi.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
204
205        if (causeCode == 0) {
206            mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
207        }
208        mLastDispatchedSmsFingerprint = null;
209    }
210
211    /**
212     * Called when the phone changes the default method updates mPhone
213     * mStorageMonitor and mCellBroadcastHandler.updatePhoneObject.
214     * Override if different or other behavior is desired.
215     *
216     * @param phone
217     */
218    @Override
219    protected void onUpdatePhoneObject(PhoneBase phone) {
220        super.onUpdatePhoneObject(phone);
221        mCellBroadcastHandler.updatePhoneObject(phone);
222    }
223
224    /**
225     * Convert Android result code to CDMA SMS failure cause.
226     * @param rc the Android SMS intent result value
227     * @return 0 for success, or a CDMA SMS failure cause value
228     */
229    private static int resultToCause(int rc) {
230        switch (rc) {
231        case Activity.RESULT_OK:
232        case Intents.RESULT_SMS_HANDLED:
233            // Cause code is ignored on success.
234            return 0;
235        case Intents.RESULT_SMS_OUT_OF_MEMORY:
236            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
237        case Intents.RESULT_SMS_UNSUPPORTED:
238            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
239        case Intents.RESULT_SMS_GENERIC_ERROR:
240        default:
241            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
242        }
243    }
244
245    /**
246     * Handle {@link SmsEnvelope#TELESERVICE_VMN} and {@link SmsEnvelope#TELESERVICE_MWI}.
247     * @param sms the message to process
248     */
249    private void handleVoicemailTeleservice(SmsMessage sms) {
250        int voicemailCount = sms.getNumOfVoicemails();
251        if (DBG) log("Voicemail count=" + voicemailCount);
252
253        // range check
254        if (voicemailCount < 0) {
255            voicemailCount = -1;
256        } else if (voicemailCount > 99) {
257            // C.S0015-B v2, 4.5.12
258            // range: 0-99
259            voicemailCount = 99;
260        }
261        // update voice mail count in phone
262        mPhone.setVoiceMessageCount(voicemailCount);
263        // store voice mail count in preferences
264        storeVoiceMailCount();
265    }
266
267    /**
268     * Processes inbound messages that are in the WAP-WDP PDU format. See
269     * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
270     * WDP segments are gathered until a datagram completes and gets dispatched.
271     *
272     * @param pdu The WAP-WDP PDU segment
273     * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
274     *         {@link Activity#RESULT_OK} if the message has been broadcast
275     *         to applications
276     */
277    private int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address,
278            long timestamp) {
279        int index = 0;
280
281        int msgType = (0xFF & pdu[index++]);
282        if (msgType != 0) {
283            log("Received a WAP SMS which is not WDP. Discard.");
284            return Intents.RESULT_SMS_HANDLED;
285        }
286        int totalSegments = (0xFF & pdu[index++]);   // >= 1
287        int segment = (0xFF & pdu[index++]);         // >= 0
288
289        if (segment >= totalSegments) {
290            loge("WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1));
291            return Intents.RESULT_SMS_HANDLED;
292        }
293
294        // Only the first segment contains sourcePort and destination Port
295        int sourcePort = 0;
296        int destinationPort = 0;
297        if (segment == 0) {
298            //process WDP segment
299            sourcePort = (0xFF & pdu[index++]) << 8;
300            sourcePort |= 0xFF & pdu[index++];
301            destinationPort = (0xFF & pdu[index++]) << 8;
302            destinationPort |= 0xFF & pdu[index++];
303            // Some carriers incorrectly send duplicate port fields in omadm wap pushes.
304            // If configured, check for that here
305            if (mCheckForDuplicatePortsInOmadmWapPush) {
306                if (checkDuplicatePortOmadmWapPush(pdu, index)) {
307                    index = index + 4; // skip duplicate port fields
308                }
309            }
310        }
311
312        // Lookup all other related parts
313        log("Received WAP PDU. Type = " + msgType + ", originator = " + address
314                + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
315                + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments);
316
317        // pass the user data portion of the PDU to the shared handler in SMSDispatcher
318        byte[] userData = new byte[pdu.length - index];
319        System.arraycopy(pdu, index, userData, 0, pdu.length - index);
320
321        InboundSmsTracker tracker = new InboundSmsTracker(userData, timestamp, destinationPort,
322                true, address, referenceNumber, segment, totalSegments, true);
323
324        return addTrackerToRawTableAndSendMessage(tracker);
325    }
326
327    /**
328     * Optional check to see if the received WapPush is an OMADM notification with erroneous
329     * extra port fields.
330     * - Some carriers make this mistake.
331     * ex: MSGTYPE-TotalSegments-CurrentSegment
332     *       -SourcePortDestPort-SourcePortDestPort-OMADM PDU
333     * @param origPdu The WAP-WDP PDU segment
334     * @param index Current Index while parsing the PDU.
335     * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
336     *         False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
337     */
338    private static boolean checkDuplicatePortOmadmWapPush(byte[] origPdu, int index) {
339        index += 4;
340        byte[] omaPdu = new byte[origPdu.length - index];
341        System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
342
343        WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu);
344        int wspIndex = 2;
345
346        // Process header length field
347        if (!pduDecoder.decodeUintvarInteger(wspIndex)) {
348            return false;
349        }
350
351        wspIndex += pduDecoder.getDecodedDataLength();  // advance to next field
352
353        // Process content type field
354        if (!pduDecoder.decodeContentType(wspIndex)) {
355            return false;
356        }
357
358        String mimeType = pduDecoder.getValueString();
359        return (WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI.equals(mimeType));
360    }
361}
362