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