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, 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