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