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