ImsSMSDispatcher.java revision 8d8e8f45200d17b78d196d8cba389fd9c547ac11
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;
18
19import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
20
21import java.util.ArrayList;
22import java.util.HashMap;
23import java.util.List;
24
25import android.app.PendingIntent;
26import android.app.PendingIntent.CanceledException;
27import android.os.AsyncResult;
28import android.os.Message;
29import android.provider.Telephony.Sms.Intents;
30import android.telephony.Rlog;
31
32import com.android.internal.telephony.cdma.CdmaSMSDispatcher;
33import com.android.internal.telephony.gsm.GsmSMSDispatcher;
34import com.android.internal.telephony.InboundSmsHandler;
35import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
36import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
37import com.android.internal.telephony.SmsBroadcastUndelivered;
38
39public final class ImsSMSDispatcher extends SMSDispatcher {
40    private static final String TAG = "RIL_ImsSms";
41
42    private SMSDispatcher mCdmaDispatcher;
43    private SMSDispatcher mGsmDispatcher;
44
45    private GsmInboundSmsHandler mGsmInboundSmsHandler;
46    private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
47
48
49    /** true if IMS is registered and sms is supported, false otherwise.*/
50    private boolean mIms = false;
51    private String mImsSmsFormat = SmsConstants.FORMAT_UNKNOWN;
52
53    public ImsSMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor,
54            SmsUsageMonitor usageMonitor) {
55        super(phone, usageMonitor, null);
56        Rlog.d(TAG, "ImsSMSDispatcher created");
57
58        // Create dispatchers, inbound SMS handlers and
59        // broadcast undelivered messages in raw table.
60        mCdmaDispatcher = new CdmaSMSDispatcher(phone, usageMonitor, this);
61        mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
62                storageMonitor, phone);
63        mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
64                storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher);
65        mGsmDispatcher = new GsmSMSDispatcher(phone, usageMonitor, this, mGsmInboundSmsHandler);
66        Thread broadcastThread = new Thread(new SmsBroadcastUndelivered(phone.getContext(),
67                mGsmInboundSmsHandler, mCdmaInboundSmsHandler));
68        broadcastThread.start();
69
70        mCi.registerForOn(this, EVENT_RADIO_ON, null);
71        mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null);
72    }
73
74    /* Updates the phone object when there is a change */
75    @Override
76    protected void updatePhoneObject(PhoneBase phone) {
77        Rlog.d(TAG, "In IMS updatePhoneObject ");
78        super.updatePhoneObject(phone);
79        mCdmaDispatcher.updatePhoneObject(phone);
80        mGsmDispatcher.updatePhoneObject(phone);
81        mGsmInboundSmsHandler.updatePhoneObject(phone);
82        mCdmaInboundSmsHandler.updatePhoneObject(phone);
83    }
84
85    public void dispose() {
86        mCi.unregisterForOn(this);
87        mCi.unregisterForImsNetworkStateChanged(this);
88        mGsmDispatcher.dispose();
89        mCdmaDispatcher.dispose();
90        mGsmInboundSmsHandler.dispose();
91        mCdmaInboundSmsHandler.dispose();
92    }
93
94    /**
95     * Handles events coming from the phone stack. Overridden from handler.
96     *
97     * @param msg the message to handle
98     */
99    @Override
100    public void handleMessage(Message msg) {
101        AsyncResult ar;
102
103        switch (msg.what) {
104        case EVENT_RADIO_ON:
105        case EVENT_IMS_STATE_CHANGED: // received unsol
106            mCi.getImsRegistrationState(this.obtainMessage(EVENT_IMS_STATE_DONE));
107            break;
108
109        case EVENT_IMS_STATE_DONE:
110            ar = (AsyncResult) msg.obj;
111
112            if (ar.exception == null) {
113                updateImsInfo(ar);
114            } else {
115                Rlog.e(TAG, "IMS State query failed with exp "
116                        + ar.exception);
117            }
118            break;
119
120        default:
121            super.handleMessage(msg);
122        }
123    }
124
125    private void setImsSmsFormat(int format) {
126        // valid format?
127        switch (format) {
128            case PhoneConstants.PHONE_TYPE_GSM:
129                mImsSmsFormat = "3gpp";
130                break;
131            case PhoneConstants.PHONE_TYPE_CDMA:
132                mImsSmsFormat = "3gpp2";
133                break;
134            default:
135                mImsSmsFormat = "unknown";
136                break;
137        }
138    }
139
140    private void updateImsInfo(AsyncResult ar) {
141        int[] responseArray = (int[])ar.result;
142
143        mIms = false;
144        if (responseArray[0] == 1) {  // IMS is registered
145            Rlog.d(TAG, "IMS is registered!");
146            mIms = true;
147        } else {
148            Rlog.d(TAG, "IMS is NOT registered!");
149        }
150
151        setImsSmsFormat(responseArray[1]);
152
153        if (("unknown".equals(mImsSmsFormat))) {
154            Rlog.e(TAG, "IMS format was unknown!");
155            // failed to retrieve valid IMS SMS format info, set IMS to unregistered
156            mIms = false;
157        }
158    }
159
160    @Override
161    protected void sendData(String destAddr, String scAddr, int destPort,
162            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
163        if (isCdmaMo()) {
164            mCdmaDispatcher.sendData(destAddr, scAddr, destPort,
165                    data, sentIntent, deliveryIntent);
166        } else {
167            mGsmDispatcher.sendData(destAddr, scAddr, destPort,
168                    data, sentIntent, deliveryIntent);
169        }
170    }
171
172    @Override
173    protected void sendMultipartText(String destAddr, String scAddr,
174            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
175            ArrayList<PendingIntent> deliveryIntents) {
176        if (isCdmaMo()) {
177            mCdmaDispatcher.sendMultipartText(destAddr, scAddr,
178                    parts, sentIntents, deliveryIntents);
179        } else {
180            mGsmDispatcher.sendMultipartText(destAddr, scAddr,
181                    parts, sentIntents, deliveryIntents);
182        }
183    }
184
185    @Override
186    protected void sendSms(SmsTracker tracker) {
187        //  sendSms is a helper function to other send functions, sendText/Data...
188        //  it is not part of ISms.stub
189        Rlog.e(TAG, "sendSms should never be called from here!");
190    }
191
192    @Override
193    protected void sendSmsByPstn(SmsTracker tracker) {
194        // This function should be defined in Gsm/CdmaDispatcher.
195        Rlog.e(TAG, "sendSmsByPstn should never be called from here!");
196    }
197
198    @Override
199    protected void updateSmsSendStatus(int messageRef, boolean success) {
200        if (isCdmaMo()) {
201            updateSmsSendStatusHelper(messageRef, mCdmaDispatcher.sendPendingList,
202                                      mCdmaDispatcher, success);
203            updateSmsSendStatusHelper(messageRef, mGsmDispatcher.sendPendingList,
204                                      null, success);
205        } else {
206            updateSmsSendStatusHelper(messageRef, mGsmDispatcher.sendPendingList,
207                                      mGsmDispatcher, success);
208            updateSmsSendStatusHelper(messageRef, mCdmaDispatcher.sendPendingList,
209                                      null, success);
210        }
211    }
212
213    /**
214     * Find a tracker in a list to update its status. If the status is successful,
215     * send an EVENT_SEND_SMS_COMPLETE message. Otherwise, resend the message by PSTN if
216     * feasible.
217     *
218     * @param messageRef the reference number of the tracker.
219     * @param sendPendingList the list of trackers to look into.
220     * @param smsDispatcher the dispatcher for resending the message by PSTN.
221     * @param success true iff the message was sent successfully.
222     */
223    private void updateSmsSendStatusHelper(int messageRef,
224                                           List<SmsTracker> sendPendingList,
225                                           SMSDispatcher smsDispatcher,
226                                           boolean success) {
227        synchronized (sendPendingList) {
228            for (int i = 0, count = sendPendingList.size(); i < count; i++) {
229                SmsTracker tracker = sendPendingList.get(i);
230                if (tracker.mMessageRef == messageRef) {
231                    // Found it.  Remove from list and broadcast.
232                    sendPendingList.remove(i);
233                    if (success) {
234                        Rlog.d(TAG, "Sending SMS by IP succeeded.");
235                        sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
236                                                  new AsyncResult(tracker, null, null)));
237                    } else {
238                        Rlog.d(TAG, "Sending SMS by IP failed.");
239                        if (smsDispatcher != null) {
240                            smsDispatcher.sendSmsByPstn(tracker);
241                        } else {
242                            Rlog.e(TAG, "No feasible way to send this SMS.");
243                        }
244                    }
245                    // Only expect to see one tracker matching this messageref.
246                    break;
247                }
248            }
249        }
250    }
251
252    @Override
253    protected void sendText(String destAddr, String scAddr, String text,
254            PendingIntent sentIntent, PendingIntent deliveryIntent) {
255        Rlog.d(TAG, "sendText");
256        if (isCdmaMo()) {
257            mCdmaDispatcher.sendText(destAddr, scAddr,
258                    text, sentIntent, deliveryIntent);
259        } else {
260            mGsmDispatcher.sendText(destAddr, scAddr,
261                    text, sentIntent, deliveryIntent);
262        }
263    }
264
265    @Override
266    public void sendRetrySms(SmsTracker tracker) {
267        String oldFormat = tracker.mFormat;
268
269        // newFormat will be based on voice technology
270        String newFormat =
271            (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType()) ?
272                    mCdmaDispatcher.getFormat() :
273                        mGsmDispatcher.getFormat();
274
275        // was previously sent sms format match with voice tech?
276        if (oldFormat.equals(newFormat)) {
277            if (isCdmaFormat(newFormat)) {
278                Rlog.d(TAG, "old format matched new format (cdma)");
279                mCdmaDispatcher.sendSms(tracker);
280                return;
281            } else {
282                Rlog.d(TAG, "old format matched new format (gsm)");
283                mGsmDispatcher.sendSms(tracker);
284                return;
285            }
286        }
287
288        // format didn't match, need to re-encode.
289        HashMap map = tracker.mData;
290
291        // to re-encode, fields needed are:  scAddr, destAddr, and
292        //   text if originally sent as sendText or
293        //   data and destPort if originally sent as sendData.
294        if (!( map.containsKey("scAddr") && map.containsKey("destAddr") &&
295               ( map.containsKey("text") ||
296                       (map.containsKey("data") && map.containsKey("destPort"))))) {
297            // should never come here...
298            Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!");
299            if (tracker.mSentIntent != null) {
300                int error = RESULT_ERROR_GENERIC_FAILURE;
301                // Done retrying; return an error to the app.
302                try {
303                    tracker.mSentIntent.send(mContext, error, null);
304                } catch (CanceledException ex) {}
305            }
306            return;
307        }
308        String scAddr = (String)map.get("scAddr");
309        String destAddr = (String)map.get("destAddr");
310
311        SmsMessageBase.SubmitPduBase pdu = null;
312        //    figure out from tracker if this was sendText/Data
313        if (map.containsKey("text")) {
314            Rlog.d(TAG, "sms failed was text");
315            String text = (String)map.get("text");
316
317            if (isCdmaFormat(newFormat)) {
318                Rlog.d(TAG, "old format (gsm) ==> new format (cdma)");
319                pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
320                        scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
321            } else {
322                Rlog.d(TAG, "old format (cdma) ==> new format (gsm)");
323                pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
324                        scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
325            }
326        } else if (map.containsKey("data")) {
327            Rlog.d(TAG, "sms failed was data");
328            byte[] data = (byte[])map.get("data");
329            Integer destPort = (Integer)map.get("destPort");
330
331            if (isCdmaFormat(newFormat)) {
332                Rlog.d(TAG, "old format (gsm) ==> new format (cdma)");
333                pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
334                            scAddr, destAddr, destPort.intValue(), data,
335                            (tracker.mDeliveryIntent != null));
336            } else {
337                Rlog.d(TAG, "old format (cdma) ==> new format (gsm)");
338                pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
339                            scAddr, destAddr, destPort.intValue(), data,
340                            (tracker.mDeliveryIntent != null));
341            }
342        }
343
344        // replace old smsc and pdu with newly encoded ones
345        map.put("smsc", pdu.encodedScAddress);
346        map.put("pdu", pdu.encodedMessage);
347
348        SMSDispatcher dispatcher = (isCdmaFormat(newFormat)) ?
349                mCdmaDispatcher : mGsmDispatcher;
350
351        tracker.mFormat = dispatcher.getFormat();
352        dispatcher.sendSms(tracker);
353    }
354
355    @Override
356    protected String getFormat() {
357        // this function should be defined in Gsm/CdmaDispatcher.
358        Rlog.e(TAG, "getFormat should never be called from here!");
359        return "unknown";
360    }
361
362    @Override
363    protected GsmAlphabet.TextEncodingDetails calculateLength(
364            CharSequence messageBody, boolean use7bitOnly) {
365        Rlog.e(TAG, "Error! Not implemented for IMS.");
366        return null;
367    }
368
369    @Override
370    protected void sendNewSubmitPdu(String destinationAddress, String scAddress, String message,
371            SmsHeader smsHeader, int format, PendingIntent sentIntent,
372            PendingIntent deliveryIntent, boolean lastPart) {
373        Rlog.e(TAG, "Error! Not implemented for IMS.");
374    }
375
376    @Override
377    public boolean isIms() {
378        return mIms;
379    }
380
381    @Override
382    public String getImsSmsFormat() {
383        return mImsSmsFormat;
384    }
385
386    /**
387     * Determines whether or not to use CDMA format for MO SMS.
388     * If SMS over IMS is supported, then format is based on IMS SMS format,
389     * otherwise format is based on current phone type.
390     *
391     * @return true if Cdma format should be used for MO SMS, false otherwise.
392     */
393    private boolean isCdmaMo() {
394        if (!isIms()) {
395            // IMS is not registered, use Voice technology to determine SMS format.
396            return (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType());
397        }
398        // IMS is registered with SMS support
399        return isCdmaFormat(mImsSmsFormat);
400    }
401
402    /**
403     * Determines whether or not format given is CDMA format.
404     *
405     * @param format
406     * @return true if format given is CDMA format, false otherwise.
407     */
408    private boolean isCdmaFormat(String format) {
409        return (mCdmaDispatcher.getFormat().equals(format));
410    }
411}
412