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