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