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 import android.app.PendingIntent; 21 import android.app.PendingIntent.CanceledException; 22 import android.net.Uri; 23 import android.os.AsyncResult; 24 import android.os.Message; 25 import android.provider.Telephony.Sms.Intents; 26 import android.telephony.Rlog; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler; 30 import com.android.internal.telephony.cdma.CdmaSMSDispatcher; 31 import com.android.internal.telephony.gsm.GsmInboundSmsHandler; 32 import com.android.internal.telephony.gsm.GsmSMSDispatcher; 33 34 import java.util.ArrayList; 35 import java.util.HashMap; 36 import java.util.concurrent.atomic.AtomicBoolean; 37 import java.util.concurrent.atomic.AtomicInteger; 38 39 public 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 ImsSMSDispatcher(Phone phone, SmsStorageMonitor storageMonitor, SmsUsageMonitor usageMonitor)53 public ImsSMSDispatcher(Phone 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 SmsBroadcastUndelivered.initialize(phone.getContext(), 67 mGsmInboundSmsHandler, mCdmaInboundSmsHandler); 68 InboundSmsHandler.registerNewMessageNotificationActionHandler(phone.getContext()); 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 updatePhoneObject(Phone phone)76 protected void updatePhoneObject(Phone 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 dispose()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 handleMessage(Message msg)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 setImsSmsFormat(int format)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 updateImsInfo(AsyncResult ar)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 sendData(String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent)161 public 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 sendMultipartText(String destAddr, String scAddr, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg, boolean persistMessage)173 public void sendMultipartText(String destAddr, String scAddr, 174 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, 175 ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg, 176 boolean persistMessage) { 177 if (isCdmaMo()) { 178 mCdmaDispatcher.sendMultipartText(destAddr, scAddr, 179 parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage); 180 } else { 181 mGsmDispatcher.sendMultipartText(destAddr, scAddr, 182 parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage); 183 } 184 } 185 186 @Override sendSms(SmsTracker tracker)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 sendSmsByPstn(SmsTracker tracker)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 sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage)200 public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, 201 PendingIntent deliveryIntent, Uri messageUri, String callingPkg, 202 boolean persistMessage) { 203 Rlog.d(TAG, "sendText"); 204 if (isCdmaMo()) { 205 mCdmaDispatcher.sendText(destAddr, scAddr, 206 text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage); 207 } else { 208 mGsmDispatcher.sendText(destAddr, scAddr, 209 text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage); 210 } 211 } 212 213 @VisibleForTesting 214 @Override injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent)215 public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) { 216 Rlog.d(TAG, "ImsSMSDispatcher:injectSmsPdu"); 217 try { 218 // TODO We need to decide whether we should allow injecting GSM(3gpp) 219 // SMS pdus when the phone is camping on CDMA(3gpp2) network and vice versa. 220 android.telephony.SmsMessage msg = 221 android.telephony.SmsMessage.createFromPdu(pdu, format); 222 223 // Only class 1 SMS are allowed to be injected. 224 if (msg == null || 225 msg.getMessageClass() != android.telephony.SmsMessage.MessageClass.CLASS_1) { 226 if (msg == null) { 227 Rlog.e(TAG, "injectSmsPdu: createFromPdu returned null"); 228 } 229 if (receivedIntent != null) { 230 receivedIntent.send(Intents.RESULT_SMS_GENERIC_ERROR); 231 } 232 return; 233 } 234 235 AsyncResult ar = new AsyncResult(receivedIntent, msg, null); 236 237 if (format.equals(SmsConstants.FORMAT_3GPP)) { 238 Rlog.i(TAG, "ImsSMSDispatcher:injectSmsText Sending msg=" + msg + 239 ", format=" + format + "to mGsmInboundSmsHandler"); 240 mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, ar); 241 } else if (format.equals(SmsConstants.FORMAT_3GPP2)) { 242 Rlog.i(TAG, "ImsSMSDispatcher:injectSmsText Sending msg=" + msg + 243 ", format=" + format + "to mCdmaInboundSmsHandler"); 244 mCdmaInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, ar); 245 } else { 246 // Invalid pdu format. 247 Rlog.e(TAG, "Invalid pdu format: " + format); 248 if (receivedIntent != null) 249 receivedIntent.send(Intents.RESULT_SMS_GENERIC_ERROR); 250 } 251 } catch (Exception e) { 252 Rlog.e(TAG, "injectSmsPdu failed: ", e); 253 try { 254 if (receivedIntent != null) 255 receivedIntent.send(Intents.RESULT_SMS_GENERIC_ERROR); 256 } catch (CanceledException ex) {} 257 } 258 } 259 260 @Override sendRetrySms(SmsTracker tracker)261 public void sendRetrySms(SmsTracker tracker) { 262 String oldFormat = tracker.mFormat; 263 264 // newFormat will be based on voice technology 265 String newFormat = 266 (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType()) ? 267 mCdmaDispatcher.getFormat() : 268 mGsmDispatcher.getFormat(); 269 270 // was previously sent sms format match with voice tech? 271 if (oldFormat.equals(newFormat)) { 272 if (isCdmaFormat(newFormat)) { 273 Rlog.d(TAG, "old format matched new format (cdma)"); 274 mCdmaDispatcher.sendSms(tracker); 275 return; 276 } else { 277 Rlog.d(TAG, "old format matched new format (gsm)"); 278 mGsmDispatcher.sendSms(tracker); 279 return; 280 } 281 } 282 283 // format didn't match, need to re-encode. 284 HashMap map = tracker.getData(); 285 286 // to re-encode, fields needed are: scAddr, destAddr, and 287 // text if originally sent as sendText or 288 // data and destPort if originally sent as sendData. 289 if (!( map.containsKey("scAddr") && map.containsKey("destAddr") && 290 ( map.containsKey("text") || 291 (map.containsKey("data") && map.containsKey("destPort"))))) { 292 // should never come here... 293 Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!"); 294 tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE, 0/*errorCode*/); 295 return; 296 } 297 String scAddr = (String)map.get("scAddr"); 298 String destAddr = (String)map.get("destAddr"); 299 300 SmsMessageBase.SubmitPduBase pdu = null; 301 // figure out from tracker if this was sendText/Data 302 if (map.containsKey("text")) { 303 Rlog.d(TAG, "sms failed was text"); 304 String text = (String)map.get("text"); 305 306 if (isCdmaFormat(newFormat)) { 307 Rlog.d(TAG, "old format (gsm) ==> new format (cdma)"); 308 pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu( 309 scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null); 310 } else { 311 Rlog.d(TAG, "old format (cdma) ==> new format (gsm)"); 312 pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu( 313 scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null); 314 } 315 } else if (map.containsKey("data")) { 316 Rlog.d(TAG, "sms failed was data"); 317 byte[] data = (byte[])map.get("data"); 318 Integer destPort = (Integer)map.get("destPort"); 319 320 if (isCdmaFormat(newFormat)) { 321 Rlog.d(TAG, "old format (gsm) ==> new format (cdma)"); 322 pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu( 323 scAddr, destAddr, destPort.intValue(), data, 324 (tracker.mDeliveryIntent != null)); 325 } else { 326 Rlog.d(TAG, "old format (cdma) ==> new format (gsm)"); 327 pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu( 328 scAddr, destAddr, destPort.intValue(), data, 329 (tracker.mDeliveryIntent != null)); 330 } 331 } 332 333 // replace old smsc and pdu with newly encoded ones 334 map.put("smsc", pdu.encodedScAddress); 335 map.put("pdu", pdu.encodedMessage); 336 337 SMSDispatcher dispatcher = (isCdmaFormat(newFormat)) ? 338 mCdmaDispatcher : mGsmDispatcher; 339 340 tracker.mFormat = dispatcher.getFormat(); 341 dispatcher.sendSms(tracker); 342 } 343 344 @Override sendSubmitPdu(SmsTracker tracker)345 protected void sendSubmitPdu(SmsTracker tracker) { 346 sendRawPdu(tracker); 347 } 348 349 @Override getFormat()350 protected String getFormat() { 351 // this function should be defined in Gsm/CdmaDispatcher. 352 Rlog.e(TAG, "getFormat should never be called from here!"); 353 return "unknown"; 354 } 355 356 @Override calculateLength( CharSequence messageBody, boolean use7bitOnly)357 protected GsmAlphabet.TextEncodingDetails calculateLength( 358 CharSequence messageBody, boolean use7bitOnly) { 359 Rlog.e(TAG, "Error! Not implemented for IMS."); 360 return null; 361 } 362 363 @Override getNewSubmitPduTracker(String destinationAddress, String scAddress, String message, SmsHeader smsHeader, int format, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart, AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri, String fullMessageText)364 protected SmsTracker getNewSubmitPduTracker(String destinationAddress, String scAddress, 365 String message, SmsHeader smsHeader, int format, PendingIntent sentIntent, 366 PendingIntent deliveryIntent, boolean lastPart, 367 AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri, 368 String fullMessageText) { 369 Rlog.e(TAG, "Error! Not implemented for IMS."); 370 return null; 371 } 372 373 @Override isIms()374 public boolean isIms() { 375 return mIms; 376 } 377 378 @Override getImsSmsFormat()379 public String getImsSmsFormat() { 380 return mImsSmsFormat; 381 } 382 383 /** 384 * Determines whether or not to use CDMA format for MO SMS. 385 * If SMS over IMS is supported, then format is based on IMS SMS format, 386 * otherwise format is based on current phone type. 387 * 388 * @return true if Cdma format should be used for MO SMS, false otherwise. 389 */ isCdmaMo()390 private boolean isCdmaMo() { 391 if (!isIms()) { 392 // IMS is not registered, use Voice technology to determine SMS format. 393 return (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType()); 394 } 395 // IMS is registered with SMS support 396 return isCdmaFormat(mImsSmsFormat); 397 } 398 399 /** 400 * Determines whether or not format given is CDMA format. 401 * 402 * @param format 403 * @return true if format given is CDMA format, false otherwise. 404 */ isCdmaFormat(String format)405 private boolean isCdmaFormat(String format) { 406 return (mCdmaDispatcher.getFormat().equals(format)); 407 } 408 } 409