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