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