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