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