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