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.List;
36import java.util.concurrent.atomic.AtomicBoolean;
37import java.util.concurrent.atomic.AtomicInteger;
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, Uri messageUri, String callingPkg) {
176        if (isCdmaMo()) {
177            mCdmaDispatcher.sendMultipartText(destAddr, scAddr,
178                    parts, sentIntents, deliveryIntents, messageUri, callingPkg);
179        } else {
180            mGsmDispatcher.sendMultipartText(destAddr, scAddr,
181                    parts, sentIntents, deliveryIntents, messageUri, callingPkg);
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 sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
200            PendingIntent deliveryIntent, Uri messageUri, String callingPkg) {
201        Rlog.d(TAG, "sendText");
202        if (isCdmaMo()) {
203            mCdmaDispatcher.sendText(destAddr, scAddr,
204                    text, sentIntent, deliveryIntent, messageUri, callingPkg);
205        } else {
206            mGsmDispatcher.sendText(destAddr, scAddr,
207                    text, sentIntent, deliveryIntent, messageUri, callingPkg);
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.mData;
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