GsmSMSDispatcher.java revision b49a73dfc4c9817bba1f227e9330555acdf9b56f
1/*
2 * Copyright (C) 2006 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.gsm;
18
19import android.app.Activity;
20import android.app.PendingIntent;
21import android.app.PendingIntent.CanceledException;
22import android.content.Intent;
23import android.os.AsyncResult;
24import android.os.Message;
25import android.provider.Telephony.Sms;
26import android.provider.Telephony.Sms.Intents;
27import android.telephony.ServiceState;
28import android.util.Config;
29import android.util.Log;
30
31import com.android.internal.telephony.CommandsInterface;
32import com.android.internal.telephony.IccUtils;
33import com.android.internal.telephony.SMSDispatcher;
34import com.android.internal.telephony.SmsHeader;
35import com.android.internal.telephony.SmsMessageBase;
36import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
37
38import java.util.ArrayList;
39import java.util.HashMap;
40
41import static android.telephony.SmsMessage.MessageClass;
42
43final class GsmSMSDispatcher extends SMSDispatcher {
44    private static final String TAG = "GSM";
45
46    private GSMPhone mGsmPhone;
47
48    GsmSMSDispatcher(GSMPhone phone) {
49        super(phone);
50        mGsmPhone = phone;
51    }
52
53    /**
54     * Called when a status report is received.  This should correspond to
55     * a previously successful SEND.
56     *
57     * @param ar AsyncResult passed into the message handler.  ar.result should
58     *           be a String representing the status report PDU, as ASCII hex.
59     */
60    protected void handleStatusReport(AsyncResult ar) {
61        String pduString = (String) ar.result;
62        SmsMessage sms = SmsMessage.newFromCDS(pduString);
63
64        if (sms != null) {
65            int tpStatus = sms.getStatus();
66            int messageRef = sms.messageRef;
67            for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
68                SmsTracker tracker = deliveryPendingList.get(i);
69                if (tracker.mMessageRef == messageRef) {
70                    // Found it.  Remove from list and broadcast.
71                    if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) {
72                       deliveryPendingList.remove(i);
73                    }
74                    PendingIntent intent = tracker.mDeliveryIntent;
75                    Intent fillIn = new Intent();
76                    fillIn.putExtra("pdu", IccUtils.hexStringToBytes(pduString));
77                    try {
78                        intent.send(mContext, Activity.RESULT_OK, fillIn);
79                    } catch (CanceledException ex) {}
80
81                    // Only expect to see one tracker matching this messageref
82                    break;
83                }
84            }
85        }
86        acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null);
87    }
88
89
90    /** {@inheritDoc} */
91    protected int dispatchMessage(SmsMessageBase smsb) {
92
93        // If sms is null, means there was a parsing error.
94        if (smsb == null) {
95            return Intents.RESULT_SMS_GENERIC_ERROR;
96        }
97        SmsMessage sms = (SmsMessage) smsb;
98        boolean handled = false;
99
100        if (sms.isTypeZero()) {
101            // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be
102            // Displayed/Stored/Notified. They should only be acknowledged.
103            Log.d(TAG, "Received short message type 0, Don't display or store it. Send Ack");
104            return Intents.RESULT_SMS_HANDLED;
105        }
106
107        // Special case the message waiting indicator messages
108        if (sms.isMWISetMessage()) {
109            mGsmPhone.updateMessageWaitingIndicator(true);
110            handled = sms.isMwiDontStore();
111            if (Config.LOGD) {
112                Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
113            }
114        } else if (sms.isMWIClearMessage()) {
115            mGsmPhone.updateMessageWaitingIndicator(false);
116            handled = sms.isMwiDontStore();
117            if (Config.LOGD) {
118                Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
119            }
120        }
121
122        if (handled) {
123            return Intents.RESULT_SMS_HANDLED;
124        }
125
126        if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) {
127            // It's a storable message and there's no storage available.  Bail.
128            // (See TS 23.038 for a description of class 0 messages.)
129            return Intents.RESULT_SMS_OUT_OF_MEMORY;
130        }
131
132        SmsHeader smsHeader = sms.getUserDataHeader();
133         // See if message is partial or port addressed.
134        if ((smsHeader == null) || (smsHeader.concatRef == null)) {
135            // Message is not partial (not part of concatenated sequence).
136            byte[][] pdus = new byte[1][];
137            pdus[0] = sms.getPdu();
138
139            if (smsHeader != null && smsHeader.portAddrs != null) {
140                if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
141                    return mWapPush.dispatchWapPdu(sms.getUserData());
142                } else {
143                    // The message was sent to a port, so concoct a URI for it.
144                    dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
145                }
146            } else {
147                // Normal short and non-port-addressed message, dispatch it.
148                dispatchPdus(pdus);
149            }
150            return Activity.RESULT_OK;
151        } else {
152            // Process the message part.
153            return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
154        }
155    }
156
157    /** {@inheritDoc} */
158    protected void sendData(String destAddr, String scAddr, int destPort,
159            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
160        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
161                scAddr, destAddr, destPort, data, (deliveryIntent != null));
162        sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
163    }
164
165    /** {@inheritDoc} */
166    protected void sendText(String destAddr, String scAddr, String text,
167            PendingIntent sentIntent, PendingIntent deliveryIntent) {
168        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
169                scAddr, destAddr, text, (deliveryIntent != null));
170        sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
171    }
172
173    /** {@inheritDoc} */
174    protected void sendMultipartText(String destinationAddress, String scAddress,
175            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
176            ArrayList<PendingIntent> deliveryIntents) {
177
178        int refNumber = getNextConcatenatedRef() & 0x00FF;
179        int msgCount = parts.size();
180        int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
181
182        mRemainingMessages = msgCount;
183
184        TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
185        for (int i = 0; i < msgCount; i++) {
186            TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
187            if (encoding != details.codeUnitSize
188                    && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
189                            || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
190                encoding = details.codeUnitSize;
191            }
192            encodingForParts[i] = details;
193        }
194
195        for (int i = 0; i < msgCount; i++) {
196            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
197            concatRef.refNumber = refNumber;
198            concatRef.seqNumber = i + 1;  // 1-based sequence
199            concatRef.msgCount = msgCount;
200            // TODO: We currently set this to true since our messaging app will never
201            // send more than 255 parts (it converts the message to MMS well before that).
202            // However, we should support 3rd party messaging apps that might need 16-bit
203            // references
204            // Note:  It's not sufficient to just flip this bit to true; it will have
205            // ripple effects (several calculations assume 8-bit ref).
206            concatRef.isEightBits = true;
207            SmsHeader smsHeader = new SmsHeader();
208            smsHeader.concatRef = concatRef;
209            if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
210                smsHeader.languageTable = encodingForParts[i].languageTable;
211                smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
212            }
213
214            PendingIntent sentIntent = null;
215            if (sentIntents != null && sentIntents.size() > i) {
216                sentIntent = sentIntents.get(i);
217            }
218
219            PendingIntent deliveryIntent = null;
220            if (deliveryIntents != null && deliveryIntents.size() > i) {
221                deliveryIntent = deliveryIntents.get(i);
222            }
223
224            SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
225                    parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
226                    encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
227
228            sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent);
229        }
230    }
231
232    /**
233     * Send a multi-part text based SMS which already passed SMS control check.
234     *
235     * It is the working function for sendMultipartText().
236     *
237     * @param destinationAddress the address to send the message to
238     * @param scAddress is the service center address or null to use
239     *   the current default SMSC
240     * @param parts an <code>ArrayList</code> of strings that, in order,
241     *   comprise the original message
242     * @param sentIntents if not null, an <code>ArrayList</code> of
243     *   <code>PendingIntent</code>s (one for each message part) that is
244     *   broadcast when the corresponding message part has been sent.
245     *   The result code will be <code>Activity.RESULT_OK<code> for success,
246     *   or one of these errors:
247     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
248     *   <code>RESULT_ERROR_RADIO_OFF</code>
249     *   <code>RESULT_ERROR_NULL_PDU</code>.
250     * @param deliveryIntents if not null, an <code>ArrayList</code> of
251     *   <code>PendingIntent</code>s (one for each message part) that is
252     *   broadcast when the corresponding message part has been delivered
253     *   to the recipient.  The raw pdu of the status report is in the
254     *   extended data ("pdu").
255     */
256    private void sendMultipartTextWithPermit(String destinationAddress,
257            String scAddress, ArrayList<String> parts,
258            ArrayList<PendingIntent> sentIntents,
259            ArrayList<PendingIntent> deliveryIntents) {
260
261        // check if in service
262        int ss = mPhone.getServiceState().getState();
263        if (ss != ServiceState.STATE_IN_SERVICE) {
264            for (int i = 0, count = parts.size(); i < count; i++) {
265                PendingIntent sentIntent = null;
266                if (sentIntents != null && sentIntents.size() > i) {
267                    sentIntent = sentIntents.get(i);
268                }
269                SmsTracker tracker = SmsTrackerFactory(null, sentIntent, null);
270                handleNotInService(ss, tracker);
271            }
272            return;
273        }
274
275        int refNumber = getNextConcatenatedRef() & 0x00FF;
276        int msgCount = parts.size();
277        int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
278
279        mRemainingMessages = msgCount;
280
281        TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
282        for (int i = 0; i < msgCount; i++) {
283            TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
284            if (encoding != details.codeUnitSize
285                    && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
286                            || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
287                encoding = details.codeUnitSize;
288            }
289            encodingForParts[i] = details;
290        }
291
292        for (int i = 0; i < msgCount; i++) {
293            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
294            concatRef.refNumber = refNumber;
295            concatRef.seqNumber = i + 1;  // 1-based sequence
296            concatRef.msgCount = msgCount;
297            concatRef.isEightBits = false;
298            SmsHeader smsHeader = new SmsHeader();
299            smsHeader.concatRef = concatRef;
300            if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
301                smsHeader.languageTable = encodingForParts[i].languageTable;
302                smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
303            }
304
305            PendingIntent sentIntent = null;
306            if (sentIntents != null && sentIntents.size() > i) {
307                sentIntent = sentIntents.get(i);
308            }
309
310            PendingIntent deliveryIntent = null;
311            if (deliveryIntents != null && deliveryIntents.size() > i) {
312                deliveryIntent = deliveryIntents.get(i);
313            }
314
315            SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
316                    parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
317                    encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
318
319            HashMap<String, Object> map = new HashMap<String, Object>();
320            map.put("smsc", pdus.encodedScAddress);
321            map.put("pdu", pdus.encodedMessage);
322
323            SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent);
324            sendSms(tracker);
325        }
326    }
327
328    /** {@inheritDoc} */
329    protected void sendSms(SmsTracker tracker) {
330        HashMap map = tracker.mData;
331
332        byte smsc[] = (byte[]) map.get("smsc");
333        byte pdu[] = (byte[]) map.get("pdu");
334
335        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
336        mCm.sendSMS(IccUtils.bytesToHexString(smsc),
337                IccUtils.bytesToHexString(pdu), reply);
338    }
339
340    /**
341     * Send the multi-part SMS based on multipart Sms tracker
342     *
343     * @param tracker holds the multipart Sms tracker ready to be sent
344     */
345    protected void sendMultipartSms (SmsTracker tracker) {
346        ArrayList<String> parts;
347        ArrayList<PendingIntent> sentIntents;
348        ArrayList<PendingIntent> deliveryIntents;
349
350        HashMap map = tracker.mData;
351
352        String destinationAddress = (String) map.get("destination");
353        String scAddress = (String) map.get("scaddress");
354
355        parts = (ArrayList<String>) map.get("parts");
356        sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents");
357        deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents");
358
359        sendMultipartTextWithPermit(destinationAddress,
360                scAddress, parts, sentIntents, deliveryIntents);
361
362    }
363
364    /** {@inheritDoc} */
365    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
366        // FIXME unit test leaves cm == null. this should change
367        if (mCm != null) {
368            mCm.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
369        }
370    }
371
372    /** {@inheritDoc} */
373    protected void activateCellBroadcastSms(int activate, Message response) {
374        // Unless CBS is implemented for GSM, this point should be unreachable.
375        Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
376        response.recycle();
377    }
378
379    /** {@inheritDoc} */
380    protected void getCellBroadcastSmsConfig(Message response){
381        // Unless CBS is implemented for GSM, this point should be unreachable.
382        Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
383        response.recycle();
384    }
385
386    /** {@inheritDoc} */
387    protected  void setCellBroadcastConfig(int[] configValuesArray, Message response) {
388        // Unless CBS is implemented for GSM, this point should be unreachable.
389        Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
390        response.recycle();
391    }
392
393    private int resultToCause(int rc) {
394        switch (rc) {
395            case Activity.RESULT_OK:
396            case Intents.RESULT_SMS_HANDLED:
397                // Cause code is ignored on success.
398                return 0;
399            case Intents.RESULT_SMS_OUT_OF_MEMORY:
400                return CommandsInterface.GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED;
401            case Intents.RESULT_SMS_GENERIC_ERROR:
402            default:
403                return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR;
404        }
405    }
406}
407