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