CdmaSMSDispatcher.java revision 059fe88115ccb70d9870698e490ed5b4c88da50c
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.AsyncResult;
29import android.os.Message;
30import android.os.SystemProperties;
31import android.preference.PreferenceManager;
32import android.provider.Telephony;
33import android.provider.Telephony.Sms.Intents;
34import android.telephony.SmsManager;
35import android.telephony.SmsMessage.MessageClass;
36import android.util.Config;
37import android.util.Log;
38
39import com.android.internal.telephony.CommandsInterface;
40import com.android.internal.telephony.SMSDispatcher;
41import com.android.internal.telephony.SmsHeader;
42import com.android.internal.telephony.SmsMessageBase;
43import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
44import com.android.internal.telephony.TelephonyProperties;
45import com.android.internal.telephony.cdma.sms.SmsEnvelope;
46import com.android.internal.telephony.cdma.sms.UserData;
47import com.android.internal.util.HexDump;
48
49import java.io.ByteArrayOutputStream;
50import java.util.ArrayList;
51import java.util.Arrays;
52import java.util.HashMap;
53
54
55final class CdmaSMSDispatcher extends SMSDispatcher {
56    private static final String TAG = "CDMA";
57
58    private byte[] mLastDispatchedSmsFingerprint;
59    private byte[] mLastAcknowledgedSmsFingerprint;
60
61    CdmaSMSDispatcher(CDMAPhone phone) {
62        super(phone);
63    }
64
65    /**
66     * Called when a status report is received.  This should correspond to
67     * a previously successful SEND.
68     * Is a special GSM function, should never be called in CDMA!!
69     *
70     * @param ar AsyncResult passed into the message handler.  ar.result should
71     *           be a String representing the status report PDU, as ASCII hex.
72     */
73    protected void handleStatusReport(AsyncResult ar) {
74        Log.d(TAG, "handleStatusReport is a special GSM function, should never be called in CDMA!");
75    }
76
77    private void handleCdmaStatusReport(SmsMessage sms) {
78        for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
79            SmsTracker tracker = deliveryPendingList.get(i);
80            if (tracker.mMessageRef == sms.messageRef) {
81                // Found it.  Remove from list and broadcast.
82                deliveryPendingList.remove(i);
83                PendingIntent intent = tracker.mDeliveryIntent;
84                Intent fillIn = new Intent();
85                fillIn.putExtra("pdu", sms.getPdu());
86                try {
87                    intent.send(mContext, Activity.RESULT_OK, fillIn);
88                } catch (CanceledException ex) {}
89                break;  // Only expect to see one tracker matching this message.
90            }
91        }
92    }
93
94    /** {@inheritDoc} */
95    protected int dispatchMessage(SmsMessageBase smsb) {
96
97        // If sms is null, means there was a parsing error.
98        if (smsb == null) {
99            Log.e(TAG, "dispatchMessage: message is null");
100            return Intents.RESULT_SMS_GENERIC_ERROR;
101        }
102
103        String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
104        if (inEcm.equals("true")) {
105            return Activity.RESULT_OK;
106        }
107
108        // See if we have a network duplicate SMS.
109        SmsMessage sms = (SmsMessage) smsb;
110        mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
111        if (mLastAcknowledgedSmsFingerprint != null &&
112                Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
113            return Intents.RESULT_SMS_HANDLED;
114        }
115        // Decode BD stream and set sms variables.
116        sms.parseSms();
117        int teleService = sms.getTeleService();
118        boolean handled = false;
119
120        if ((SmsEnvelope.TELESERVICE_VMN == teleService) ||
121                (SmsEnvelope.TELESERVICE_MWI == teleService)) {
122            // handling Voicemail
123            int voicemailCount = sms.getNumOfVoicemails();
124            Log.d(TAG, "Voicemail count=" + voicemailCount);
125            // Store the voicemail count in preferences.
126            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
127                    mPhone.getContext());
128            SharedPreferences.Editor editor = sp.edit();
129            editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
130            editor.commit();
131            ((CDMAPhone) mPhone).updateMessageWaitingIndicator(voicemailCount);
132            handled = true;
133        } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) ||
134                (SmsEnvelope.TELESERVICE_WEMT == teleService)) &&
135                sms.isStatusReportMessage()) {
136            handleCdmaStatusReport(sms);
137            handled = true;
138        } else if ((sms.getUserData() == null)) {
139            if (Config.LOGD) {
140                Log.d(TAG, "Received SMS without user data");
141            }
142            handled = true;
143        }
144
145        if (handled) {
146            return Intents.RESULT_SMS_HANDLED;
147        }
148
149        if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) {
150            // It's a storable message and there's no storage available.  Bail.
151            // (See C.S0015-B v2.0 for a description of "Immediate Display"
152            // messages, which we represent as CLASS_0.)
153            return Intents.RESULT_SMS_OUT_OF_MEMORY;
154        }
155
156        if (SmsEnvelope.TELESERVICE_WAP == teleService) {
157            return processCdmaWapPdu(sms.getUserData(), sms.messageRef,
158                    sms.getOriginatingAddress());
159        }
160
161        // Reject (NAK) any messages with teleservice ids that have
162        // not yet been handled and also do not correspond to the two
163        // kinds that are processed below.
164        if ((SmsEnvelope.TELESERVICE_WMT != teleService) &&
165                (SmsEnvelope.TELESERVICE_WEMT != teleService) &&
166                (SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) {
167            return Intents.RESULT_SMS_UNSUPPORTED;
168        }
169
170        /*
171         * TODO(cleanup): Why are we using a getter method for this
172         * (and for so many other sms fields)?  Trivial getters and
173         * setters like this are direct violations of the style guide.
174         * If the purpose is to protect against writes (by not
175         * providing a setter) then any protection is illusory (and
176         * hence bad) for cases where the values are not primitives,
177         * such as this call for the header.  Since this is an issue
178         * with the public API it cannot be changed easily, but maybe
179         * something can be done eventually.
180         */
181        SmsHeader smsHeader = sms.getUserDataHeader();
182
183        /*
184         * TODO(cleanup): Since both CDMA and GSM use the same header
185         * format, this dispatch processing is naturally identical,
186         * and code should probably not be replicated explicitly.
187         */
188
189        // See if message is partial or port addressed.
190        if ((smsHeader == null) || (smsHeader.concatRef == null)) {
191            // Message is not partial (not part of concatenated sequence).
192            byte[][] pdus = new byte[1][];
193            pdus[0] = sms.getPdu();
194
195            if (smsHeader != null && smsHeader.portAddrs != null) {
196                if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
197                    // GSM-style WAP indication
198                    return mWapPush.dispatchWapPdu(sms.getUserData());
199                } else {
200                    // The message was sent to a port, so concoct a URI for it.
201                    dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
202                }
203            } else {
204                // Normal short and non-port-addressed message, dispatch it.
205                dispatchPdus(pdus);
206            }
207            return Activity.RESULT_OK;
208        } else {
209            // Process the message part.
210            return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
211        }
212    }
213
214    /**
215     * Processes inbound messages that are in the WAP-WDP PDU format. See
216     * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
217     * WDP segments are gathered until a datagram completes and gets dispatched.
218     *
219     * @param pdu The WAP-WDP PDU segment
220     * @return a result code from {@link Telephony.Sms.Intents}, or
221     *         {@link Activity#RESULT_OK} if the message has been broadcast
222     *         to applications
223     */
224    protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) {
225        int segment;
226        int totalSegments;
227        int index = 0;
228        int msgType;
229
230        int sourcePort = 0;
231        int destinationPort = 0;
232
233        msgType = pdu[index++];
234        if (msgType != 0){
235            Log.w(TAG, "Received a WAP SMS which is not WDP. Discard.");
236            return Intents.RESULT_SMS_HANDLED;
237        }
238        totalSegments = pdu[index++]; // >=1
239        segment = pdu[index++]; // >=0
240
241        // Only the first segment contains sourcePort and destination Port
242        if (segment == 0) {
243            //process WDP segment
244            sourcePort = (0xFF & pdu[index++]) << 8;
245            sourcePort |= 0xFF & pdu[index++];
246            destinationPort = (0xFF & pdu[index++]) << 8;
247            destinationPort |= 0xFF & pdu[index++];
248        }
249
250        // Lookup all other related parts
251        StringBuilder where = new StringBuilder("reference_number =");
252        where.append(referenceNumber);
253        where.append(" AND address = ?");
254        String[] whereArgs = new String[] {address};
255
256        Log.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address
257                + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
258                + ", ID = " + referenceNumber + ", segment# = " + segment + "/" + totalSegments);
259
260        byte[][] pdus = null;
261        Cursor cursor = null;
262        try {
263            cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
264            int cursorCount = cursor.getCount();
265            if (cursorCount != totalSegments - 1) {
266                // We don't have all the parts yet, store this one away
267                ContentValues values = new ContentValues();
268                values.put("date", new Long(0));
269                values.put("pdu", HexDump.toHexString(pdu, index, pdu.length - index));
270                values.put("address", address);
271                values.put("reference_number", referenceNumber);
272                values.put("count", totalSegments);
273                values.put("sequence", segment);
274                values.put("destination_port", destinationPort);
275
276                mResolver.insert(mRawUri, values);
277
278                return Intents.RESULT_SMS_HANDLED;
279            }
280
281            // All the parts are in place, deal with them
282            int pduColumn = cursor.getColumnIndex("pdu");
283            int sequenceColumn = cursor.getColumnIndex("sequence");
284
285            pdus = new byte[totalSegments][];
286            for (int i = 0; i < cursorCount; i++) {
287                cursor.moveToNext();
288                int cursorSequence = (int)cursor.getLong(sequenceColumn);
289                // Read the destination port from the first segment
290                if (cursorSequence == 0) {
291                    int destinationPortColumn = cursor.getColumnIndex("destination_port");
292                    destinationPort = (int)cursor.getLong(destinationPortColumn);
293                }
294                pdus[cursorSequence] = HexDump.hexStringToByteArray(
295                        cursor.getString(pduColumn));
296            }
297            // The last part will be added later
298
299            // Remove the parts from the database
300            mResolver.delete(mRawUri, where.toString(), whereArgs);
301        } catch (SQLException e) {
302            Log.e(TAG, "Can't access multipart SMS database", e);
303            return Intents.RESULT_SMS_GENERIC_ERROR;
304        } finally {
305            if (cursor != null) cursor.close();
306        }
307
308        // Build up the data stream
309        ByteArrayOutputStream output = new ByteArrayOutputStream();
310        for (int i = 0; i < totalSegments; i++) {
311            // reassemble the (WSP-)pdu
312            if (i == segment) {
313                // This one isn't in the DB, so add it
314                output.write(pdu, index, pdu.length - index);
315            } else {
316                output.write(pdus[i], 0, pdus[i].length);
317            }
318        }
319
320        byte[] datagram = output.toByteArray();
321        // Dispatch the PDU to applications
322        switch (destinationPort) {
323        case SmsHeader.PORT_WAP_PUSH:
324            // Handle the PUSH
325            return mWapPush.dispatchWapPdu(datagram);
326
327        default:{
328            pdus = new byte[1][];
329            pdus[0] = datagram;
330            // The messages were sent to any other WAP port
331            dispatchPortAddressedPdus(pdus, destinationPort);
332            return Activity.RESULT_OK;
333        }
334        }
335    }
336
337    /** {@inheritDoc} */
338    protected void sendData(String destAddr, String scAddr, int destPort,
339            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
340        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
341                scAddr, destAddr, destPort, data, (deliveryIntent != null));
342        sendSubmitPdu(pdu, sentIntent, deliveryIntent);
343    }
344
345    /** {@inheritDoc} */
346    protected void sendText(String destAddr, String scAddr, String text,
347            PendingIntent sentIntent, PendingIntent deliveryIntent) {
348        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
349                scAddr, destAddr, text, (deliveryIntent != null), null);
350        sendSubmitPdu(pdu, sentIntent, deliveryIntent);
351    }
352
353    /** {@inheritDoc} */
354    protected void sendMultipartText(String destAddr, String scAddr,
355            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
356            ArrayList<PendingIntent> deliveryIntents) {
357
358        /**
359         * TODO(cleanup): There is no real code difference between
360         * this and the GSM version, and hence it should be moved to
361         * the base class or consolidated somehow, provided calling
362         * the proper submitpdu stuff can be arranged.
363         */
364
365        int refNumber = getNextConcatenatedRef() & 0x00FF;
366        int msgCount = parts.size();
367        int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
368
369        for (int i = 0; i < msgCount; i++) {
370            TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
371            if (encoding != details.codeUnitSize
372                    && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
373                            || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
374                encoding = details.codeUnitSize;
375            }
376        }
377
378        for (int i = 0; i < msgCount; i++) {
379            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
380            concatRef.refNumber = refNumber;
381            concatRef.seqNumber = i + 1;  // 1-based sequence
382            concatRef.msgCount = msgCount;
383            concatRef.isEightBits = true;
384            SmsHeader smsHeader = new SmsHeader();
385            smsHeader.concatRef = concatRef;
386
387            PendingIntent sentIntent = null;
388            if (sentIntents != null && sentIntents.size() > i) {
389                sentIntent = sentIntents.get(i);
390            }
391
392            PendingIntent deliveryIntent = null;
393            if (deliveryIntents != null && deliveryIntents.size() > i) {
394                deliveryIntent = deliveryIntents.get(i);
395            }
396
397            UserData uData = new UserData();
398            uData.payloadStr = parts.get(i);
399            uData.userDataHeader = smsHeader;
400            if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
401                uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
402            } else { // assume UTF-16
403                uData.msgEncoding = UserData.ENCODING_UNICODE_16;
404            }
405            uData.msgEncodingSet = true;
406
407            /* By setting the statusReportRequested bit only for the
408             * last message fragment, this will result in only one
409             * callback to the sender when that last fragment delivery
410             * has been acknowledged. */
411            SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destAddr,
412                    uData, (deliveryIntent != null) && (i == (msgCount - 1)));
413
414            sendSubmitPdu(submitPdu, sentIntent, deliveryIntent);
415        }
416    }
417
418    protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
419            PendingIntent sentIntent, PendingIntent deliveryIntent) {
420        if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
421            if (sentIntent != null) {
422                try {
423                    sentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
424                } catch (CanceledException ex) {}
425            }
426            if (Config.LOGD) {
427                Log.d(TAG, "Block SMS in Emergency Callback mode");
428            }
429            return;
430        }
431        sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
432    }
433
434    /** {@inheritDoc} */
435    protected void sendSms(SmsTracker tracker) {
436        HashMap map = tracker.mData;
437
438        // byte smsc[] = (byte[]) map.get("smsc");  // unused for CDMA
439        byte pdu[] = (byte[]) map.get("pdu");
440
441        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
442
443        mCm.sendCdmaSms(pdu, reply);
444    }
445
446     /** {@inheritDoc} */
447    protected void sendMultipartSms (SmsTracker tracker) {
448        Log.d(TAG, "TODO: CdmaSMSDispatcher.sendMultipartSms not implemented");
449    }
450
451    /** {@inheritDoc} */
452    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
453        // FIXME unit test leaves cm == null. this should change
454
455        String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
456        if (inEcm.equals("true")) {
457            return;
458        }
459
460        if (mCm != null) {
461            int causeCode = resultToCause(result);
462            mCm.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
463
464            if (causeCode == 0) {
465                mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
466            }
467            mLastDispatchedSmsFingerprint = null;
468        }
469    }
470
471    /** {@inheritDoc} */
472    protected void activateCellBroadcastSms(int activate, Message response) {
473        mCm.setCdmaBroadcastActivation((activate == 0), response);
474    }
475
476    /** {@inheritDoc} */
477    protected void getCellBroadcastSmsConfig(Message response) {
478        mCm.getCdmaBroadcastConfig(response);
479    }
480
481    /** {@inheritDoc} */
482    protected void setCellBroadcastConfig(int[] configValuesArray, Message response) {
483        mCm.setCdmaBroadcastConfig(configValuesArray, response);
484    }
485
486    private int resultToCause(int rc) {
487        switch (rc) {
488        case Activity.RESULT_OK:
489        case Intents.RESULT_SMS_HANDLED:
490            // Cause code is ignored on success.
491            return 0;
492        case Intents.RESULT_SMS_OUT_OF_MEMORY:
493            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
494        case Intents.RESULT_SMS_UNSUPPORTED:
495            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
496        case Intents.RESULT_SMS_GENERIC_ERROR:
497        default:
498            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
499        }
500    }
501}
502