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