Home | History | Annotate | Download | only in telephony
      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 
     17 package com.android.internal.telephony;
     18 
     19 import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
     20 
     21 import java.util.ArrayList;
     22 import java.util.HashMap;
     23 import java.util.List;
     24 import java.util.concurrent.atomic.AtomicBoolean;
     25 import java.util.concurrent.atomic.AtomicInteger;
     26 
     27 import android.app.PendingIntent;
     28 import android.app.PendingIntent.CanceledException;
     29 import android.net.Uri;
     30 import android.os.AsyncResult;
     31 import android.os.Message;
     32 import android.provider.Telephony.Sms.Intents;
     33 import android.telephony.Rlog;
     34 
     35 import com.android.internal.telephony.cdma.CdmaSMSDispatcher;
     36 import com.android.internal.telephony.gsm.GsmSMSDispatcher;
     37 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
     38 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
     39 
     40 public 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, Uri messageUri, String callingPkg) {
    177         if (isCdmaMo()) {
    178             mCdmaDispatcher.sendMultipartText(destAddr, scAddr,
    179                     parts, sentIntents, deliveryIntents, messageUri, callingPkg);
    180         } else {
    181             mGsmDispatcher.sendMultipartText(destAddr, scAddr,
    182                     parts, sentIntents, deliveryIntents, messageUri, callingPkg);
    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, PendingIntent sentIntent,
    255             PendingIntent deliveryIntent, Uri messageUri, String callingPkg) {
    256         Rlog.d(TAG, "sendText");
    257         if (isCdmaMo()) {
    258             mCdmaDispatcher.sendText(destAddr, scAddr,
    259                     text, sentIntent, deliveryIntent, messageUri, callingPkg);
    260         } else {
    261             mGsmDispatcher.sendText(destAddr, scAddr,
    262                     text, sentIntent, deliveryIntent, messageUri, callingPkg);
    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