1 /* 2 * Copyright (C) 2018 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 android.app.Activity; 20 import android.content.Context; 21 import android.os.Binder; 22 import android.os.Message; 23 import android.os.PersistableBundle; 24 import android.os.RemoteException; 25 import android.provider.Telephony.Sms.Intents; 26 import android.telephony.CarrierConfigManager; 27 import android.telephony.ServiceState; 28 import android.telephony.SmsManager; 29 import android.telephony.ims.ImsReasonInfo; 30 import android.telephony.ims.RegistrationManager; 31 import android.telephony.ims.aidl.IImsSmsListener; 32 import android.telephony.ims.feature.MmTelFeature; 33 import android.telephony.ims.stub.ImsRegistrationImplBase; 34 import android.telephony.ims.stub.ImsSmsImplBase; 35 import android.telephony.ims.stub.ImsSmsImplBase.SendStatusResult; 36 37 import com.android.ims.FeatureConnector; 38 import com.android.ims.ImsException; 39 import com.android.ims.ImsManager; 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; 42 import com.android.internal.telephony.analytics.TelephonyAnalytics; 43 import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics; 44 import com.android.internal.telephony.metrics.TelephonyMetrics; 45 import com.android.internal.telephony.uicc.IccUtils; 46 import com.android.internal.telephony.util.SMSDispatcherUtil; 47 import com.android.telephony.Rlog; 48 49 import java.util.ArrayList; 50 import java.util.HashMap; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.concurrent.ConcurrentHashMap; 54 import java.util.concurrent.Executor; 55 import java.util.concurrent.atomic.AtomicInteger; 56 57 /** 58 * Responsible for communications with {@link com.android.ims.ImsManager} to send/receive messages 59 * over IMS. 60 * @hide 61 */ 62 public class ImsSmsDispatcher extends SMSDispatcher { 63 64 private static final String TAG = "ImsSmsDispatcher"; 65 private static final int CONNECT_DELAY_MS = 5000; // 5 seconds; 66 public static final int MAX_SEND_RETRIES_OVER_IMS = MAX_SEND_RETRIES; 67 68 /** 69 * Creates FeatureConnector instances for ImsManager, used during testing to inject mock 70 * connector instances. 71 */ 72 @VisibleForTesting 73 public interface FeatureConnectorFactory { 74 /** 75 * Create a new FeatureConnector for ImsManager. 76 */ create(Context context, int phoneId, String logPrefix, FeatureConnector.Listener<ImsManager> listener, Executor executor)77 FeatureConnector<ImsManager> create(Context context, int phoneId, String logPrefix, 78 FeatureConnector.Listener<ImsManager> listener, Executor executor); 79 } 80 81 public List<Integer> mMemoryAvailableNotifierList = new ArrayList<Integer>(); 82 @VisibleForTesting 83 public Map<Integer, SmsTracker> mTrackers = new ConcurrentHashMap<>(); 84 @VisibleForTesting 85 public AtomicInteger mNextToken = new AtomicInteger(); 86 private final Object mLock = new Object(); 87 private volatile boolean mIsSmsCapable; 88 private volatile boolean mIsImsServiceUp; 89 private volatile boolean mIsRegistered; 90 private final FeatureConnector<ImsManager> mImsManagerConnector; 91 /** Telephony metrics instance for logging metrics event */ 92 private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance(); 93 private ImsManager mImsManager; 94 private FeatureConnectorFactory mConnectorFactory; 95 96 private Runnable mConnectRunnable = new Runnable() { 97 @Override 98 public void run() { 99 mImsManagerConnector.connect(); 100 } 101 }; 102 103 /** 104 * Listen to the IMS service state change 105 * 106 */ 107 private RegistrationManager.RegistrationCallback mRegistrationCallback = 108 new RegistrationManager.RegistrationCallback() { 109 @Override 110 public void onRegistered( 111 @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) { 112 logd("onImsConnected imsRadioTech=" + imsRadioTech); 113 synchronized (mLock) { 114 mIsRegistered = true; 115 } 116 } 117 118 @Override 119 public void onRegistering( 120 @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) { 121 logd("onImsProgressing imsRadioTech=" + imsRadioTech); 122 synchronized (mLock) { 123 mIsRegistered = false; 124 } 125 } 126 127 @Override 128 public void onUnregistered(ImsReasonInfo info) { 129 logd("onImsDisconnected imsReasonInfo=" + info); 130 synchronized (mLock) { 131 mIsRegistered = false; 132 } 133 } 134 }; 135 136 private android.telephony.ims.ImsMmTelManager.CapabilityCallback mCapabilityCallback = 137 new android.telephony.ims.ImsMmTelManager.CapabilityCallback() { 138 @Override 139 public void onCapabilitiesStatusChanged( 140 MmTelFeature.MmTelCapabilities capabilities) { 141 synchronized (mLock) { 142 mIsSmsCapable = capabilities.isCapable( 143 MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS); 144 } 145 } 146 }; 147 148 private final IImsSmsListener mImsSmsListener = new IImsSmsListener.Stub() { 149 @Override 150 public void onMemoryAvailableResult(int token, @SendStatusResult int status, 151 int networkReasonCode) { 152 final long identity = Binder.clearCallingIdentity(); 153 try { 154 logd("onMemoryAvailableResult token=" + token + " status=" + status 155 + " networkReasonCode=" + networkReasonCode); 156 if (!mMemoryAvailableNotifierList.contains(token)) { 157 loge("onMemoryAvailableResult Invalid token"); 158 return; 159 } 160 mMemoryAvailableNotifierList.remove(Integer.valueOf(token)); 161 162 /** 163 * The Retrans flag is set and reset As per section 6.3.3.1.2 in TS 124011 164 * Note: Assuming that SEND_STATUS_ERROR_RETRY is sent in case of temporary failure 165 */ 166 if (status == ImsSmsImplBase.SEND_STATUS_ERROR_RETRY) { 167 if (!mRPSmmaRetried) { 168 sendMessageDelayed(obtainMessage(EVENT_RETRY_SMMA), SEND_RETRY_DELAY); 169 mRPSmmaRetried = true; 170 } else { 171 mRPSmmaRetried = false; 172 } 173 } else { 174 mRPSmmaRetried = false; 175 } 176 } finally { 177 Binder.restoreCallingIdentity(identity); 178 } 179 } 180 @Override 181 public void onSendSmsResult(int token, int messageRef, @SendStatusResult int status, 182 @SmsManager.Result int reason, int networkReasonCode) { 183 final long identity = Binder.clearCallingIdentity(); 184 try { 185 logd("onSendSmsResult token=" + token + " messageRef=" + messageRef 186 + " status=" + status + " reason=" + reason + " networkReasonCode=" 187 + networkReasonCode); 188 // TODO integrate networkReasonCode into IMS SMS metrics. 189 SmsTracker tracker = mTrackers.get(token); 190 mMetrics.writeOnImsServiceSmsSolicitedResponse(mPhone.getPhoneId(), status, reason, 191 (tracker != null ? tracker.mMessageId : 0L)); 192 if (tracker == null) { 193 throw new IllegalArgumentException("Invalid token."); 194 } 195 tracker.mMessageRef = messageRef; 196 switch(status) { 197 case ImsSmsImplBase.SEND_STATUS_OK: 198 if (tracker.mDeliveryIntent != null) { 199 // Expecting a status report. Put this tracker to the map. 200 mSmsDispatchersController.putDeliveryPendingTracker(tracker); 201 } 202 tracker.onSent(mContext); 203 mTrackers.remove(token); 204 mPhone.notifySmsSent(tracker.mDestAddress); 205 mSmsDispatchersController.notifySmsSentToEmergencyStateTracker( 206 tracker.mDestAddress, tracker.mMessageId, true, 207 tracker.isSinglePartOrLastPart()); 208 break; 209 case ImsSmsImplBase.SEND_STATUS_ERROR: 210 tracker.onFailed(mContext, reason, networkReasonCode); 211 mTrackers.remove(token); 212 notifySmsSentFailedToEmergencyStateTracker(tracker, true); 213 break; 214 case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY: 215 int maxRetryCountOverIms = getMaxRetryCountOverIms(); 216 if (tracker.mRetryCount < getMaxSmsRetryCount()) { 217 if (maxRetryCountOverIms < getMaxSmsRetryCount() 218 && tracker.mRetryCount >= maxRetryCountOverIms) { 219 tracker.mRetryCount += 1; 220 mTrackers.remove(token); 221 fallbackToPstn(tracker); 222 break; 223 } 224 tracker.mRetryCount += 1; 225 sendMessageDelayed( 226 obtainMessage(EVENT_SEND_RETRY, tracker), 227 getSmsRetryDelayValue()); 228 } else { 229 tracker.onFailed(mContext, reason, networkReasonCode); 230 mTrackers.remove(token); 231 notifySmsSentFailedToEmergencyStateTracker(tracker, true); 232 } 233 break; 234 case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK: 235 // Skip MAX_SEND_RETRIES checking here. It allows CSFB after 236 // SEND_STATUS_ERROR_RETRY up to MAX_SEND_RETRIES even. 237 tracker.mRetryCount += 1; 238 mTrackers.remove(token); 239 fallbackToPstn(tracker); 240 break; 241 default: 242 } 243 mPhone.getSmsStats().onOutgoingSms( 244 true /* isOverIms */, 245 SmsConstants.FORMAT_3GPP2.equals(getFormat()), 246 status == ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, 247 reason, 248 networkReasonCode, 249 tracker.mMessageId, 250 tracker.isFromDefaultSmsApplication(mContext), 251 tracker.getInterval(), 252 mTelephonyManager.isEmergencyNumber(tracker.mDestAddress)); 253 if (mPhone != null) { 254 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics(); 255 if (telephonyAnalytics != null) { 256 SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics(); 257 if (smsMmsAnalytics != null) { 258 smsMmsAnalytics.onOutgoingSms( 259 true /* isOverIms */, 260 reason); 261 } 262 } 263 } 264 265 } finally { 266 Binder.restoreCallingIdentity(identity); 267 } 268 } 269 270 @Override 271 public void onSmsStatusReportReceived(int token, String format, byte[] pdu) 272 throws RemoteException { 273 final long identity = Binder.clearCallingIdentity(); 274 try { 275 logd("Status report received."); 276 android.telephony.SmsMessage message = 277 android.telephony.SmsMessage.createFromPdu(pdu, format); 278 if (message == null || message.mWrappedSmsMessage == null) { 279 throw new RemoteException( 280 "Status report received with a PDU that could not be parsed."); 281 } 282 mSmsDispatchersController.handleSmsStatusReport(format, pdu); 283 try { 284 getImsManager().acknowledgeSmsReport( 285 token, 286 message.mWrappedSmsMessage.mMessageRef, 287 ImsSmsImplBase.STATUS_REPORT_STATUS_OK); 288 } catch (ImsException e) { 289 loge("Failed to acknowledgeSmsReport(). Error: " + e.getMessage()); 290 } 291 } finally { 292 Binder.restoreCallingIdentity(identity); 293 } 294 } 295 296 @Override 297 public void onSmsReceived(int token, String format, byte[] pdu) { 298 final long identity = Binder.clearCallingIdentity(); 299 try { 300 logd("SMS received."); 301 android.telephony.SmsMessage message = 302 android.telephony.SmsMessage.createFromPdu(pdu, format); 303 mSmsDispatchersController.injectSmsPdu(message, format, result -> { 304 logd("SMS handled result: " + result); 305 int mappedResult; 306 switch (result) { 307 case Intents.RESULT_SMS_HANDLED: 308 mappedResult = ImsSmsImplBase.DELIVER_STATUS_OK; 309 if (message != null) { 310 mSmsDispatchersController 311 .notifySmsReceivedViaImsToEmergencyStateTracker( 312 message.getOriginatingAddress()); 313 } 314 break; 315 case Intents.RESULT_SMS_OUT_OF_MEMORY: 316 mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_NO_MEMORY; 317 break; 318 case Intents.RESULT_SMS_UNSUPPORTED: 319 mappedResult = 320 ImsSmsImplBase.DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED; 321 break; 322 case Activity.RESULT_OK: 323 // class2 message saving to SIM operation is in progress, defer ack 324 // until saving to SIM is success/failure 325 return; 326 default: 327 mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC; 328 break; 329 } 330 try { 331 if (message != null && message.mWrappedSmsMessage != null) { 332 getImsManager().acknowledgeSms(token, 333 message.mWrappedSmsMessage.mMessageRef, mappedResult); 334 } else { 335 logw("SMS Received with a PDU that could not be parsed."); 336 getImsManager().acknowledgeSms(token, 0, mappedResult); 337 } 338 } catch (ImsException e) { 339 loge("Failed to acknowledgeSms(). Error: " + e.getMessage()); 340 } 341 }, true /* ignoreClass */, true /* isOverIms */, token); 342 } finally { 343 Binder.restoreCallingIdentity(identity); 344 } 345 } 346 }; 347 348 @Override handleMessage(Message msg)349 public void handleMessage(Message msg) { 350 switch (msg.what) { 351 case EVENT_SEND_RETRY: 352 logd("SMS retry.."); 353 sendSms((SmsTracker) msg.obj); 354 break; 355 case EVENT_RETRY_SMMA: 356 logd("SMMA Notification retry.."); 357 onMemoryAvailable(); 358 break; 359 default: 360 super.handleMessage(msg); 361 } 362 } 363 ImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController, FeatureConnectorFactory factory)364 public ImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController, 365 FeatureConnectorFactory factory) { 366 super(phone, smsDispatchersController); 367 mConnectorFactory = factory; 368 369 mImsManagerConnector = mConnectorFactory.create(mContext, mPhone.getPhoneId(), TAG, 370 new FeatureConnector.Listener<ImsManager>() { 371 public void connectionReady(ImsManager manager, int subId) throws ImsException { 372 logd("ImsManager: connection ready."); 373 synchronized (mLock) { 374 mImsManager = manager; 375 setListeners(); 376 mIsImsServiceUp = true; 377 378 /* set ImsManager */ 379 mSmsDispatchersController.setImsManager(mImsManager); 380 } 381 } 382 383 @Override 384 public void connectionUnavailable(int reason) { 385 logd("ImsManager: connection unavailable, reason=" + reason); 386 if (reason == FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE) { 387 loge("connectionUnavailable: unexpected, received server error"); 388 removeCallbacks(mConnectRunnable); 389 postDelayed(mConnectRunnable, CONNECT_DELAY_MS); 390 } 391 synchronized (mLock) { 392 mImsManager = null; 393 mIsImsServiceUp = false; 394 395 /* unset ImsManager */ 396 mSmsDispatchersController.setImsManager(null); 397 } 398 } 399 }, this::post); 400 post(mConnectRunnable); 401 } 402 setListeners()403 private void setListeners() throws ImsException { 404 getImsManager().addRegistrationCallback(mRegistrationCallback, this::post); 405 getImsManager().addCapabilitiesCallback(mCapabilityCallback, this::post); 406 getImsManager().setSmsListener(getSmsListener()); 407 getImsManager().onSmsReady(); 408 } 409 isLteService()410 private boolean isLteService() { 411 return ((mPhone.getServiceState().getRilDataRadioTechnology() == 412 ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && (mPhone.getServiceState(). 413 getDataRegistrationState() == ServiceState.STATE_IN_SERVICE)); 414 } 415 isLimitedLteService()416 private boolean isLimitedLteService() { 417 return ((mPhone.getServiceState().getRilVoiceRadioTechnology() == 418 ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && mPhone.getServiceState().isEmergencyOnly()); 419 } 420 isEmergencySmsPossible()421 private boolean isEmergencySmsPossible() { 422 return isLteService() || isLimitedLteService(); 423 } 424 isEmergencySmsSupport(String destAddr)425 public boolean isEmergencySmsSupport(String destAddr) { 426 PersistableBundle b; 427 boolean eSmsCarrierSupport = false; 428 if (!mTelephonyManager.isEmergencyNumber(destAddr)) { 429 logi(Rlog.pii(TAG, destAddr) + " is not emergency number"); 430 return false; 431 } 432 433 final long identity = Binder.clearCallingIdentity(); 434 try { 435 CarrierConfigManager configManager = (CarrierConfigManager) mContext 436 .getSystemService(Context.CARRIER_CONFIG_SERVICE); 437 if (configManager == null) { 438 loge("configManager is null"); 439 return false; 440 } 441 b = configManager.getConfigForSubId(getSubId()); 442 if (b == null) { 443 loge("PersistableBundle is null"); 444 return false; 445 } 446 eSmsCarrierSupport = b.getBoolean( 447 CarrierConfigManager.KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL); 448 boolean lteOrLimitedLte = isEmergencySmsPossible(); 449 logi("isEmergencySmsSupport emergencySmsCarrierSupport: " 450 + eSmsCarrierSupport + " destAddr: " + Rlog.pii(TAG, destAddr) 451 + " mIsImsServiceUp: " + mIsImsServiceUp + " lteOrLimitedLte: " 452 + lteOrLimitedLte); 453 454 return eSmsCarrierSupport && mIsImsServiceUp && lteOrLimitedLte; 455 } finally { 456 Binder.restoreCallingIdentity(identity); 457 } 458 } 459 isAvailable()460 public boolean isAvailable() { 461 synchronized (mLock) { 462 logd("isAvailable: up=" + mIsImsServiceUp + ", reg= " + mIsRegistered 463 + ", cap= " + mIsSmsCapable); 464 return mIsImsServiceUp && mIsRegistered && mIsSmsCapable; 465 } 466 } 467 468 @Override getFormat()469 protected String getFormat() { 470 // This is called in the constructor before ImsSmsDispatcher has a chance to initialize 471 // mLock. ImsManager will not be up anyway at this point, so report UNKNOWN. 472 if (mLock == null) return SmsConstants.FORMAT_UNKNOWN; 473 try { 474 return getImsManager().getSmsFormat(); 475 } catch (ImsException e) { 476 loge("Failed to get sms format. Error: " + e.getMessage()); 477 return SmsConstants.FORMAT_UNKNOWN; 478 } 479 } 480 481 @Override getMaxSmsRetryCount()482 public int getMaxSmsRetryCount() { 483 int retryCount = MAX_SEND_RETRIES; 484 CarrierConfigManager mConfigManager; 485 486 mConfigManager = (CarrierConfigManager) mContext.getSystemService( 487 Context.CARRIER_CONFIG_SERVICE); 488 489 if (mConfigManager != null) { 490 PersistableBundle carrierConfig = mConfigManager.getConfigForSubId( 491 getSubId()); 492 493 if (carrierConfig != null) { 494 retryCount = carrierConfig.getInt( 495 CarrierConfigManager.ImsSms.KEY_SMS_MAX_RETRY_COUNT_INT); 496 } 497 } 498 499 Rlog.d(TAG, "Retry Count: " + retryCount); 500 501 return retryCount; 502 } 503 504 /** 505 * Returns the number of times SMS can be sent over IMS 506 * 507 * @return retry count over Ims from carrier configuration 508 */ 509 @VisibleForTesting getMaxRetryCountOverIms()510 public int getMaxRetryCountOverIms() { 511 int retryCountOverIms = MAX_SEND_RETRIES_OVER_IMS; 512 CarrierConfigManager mConfigManager; 513 514 mConfigManager = (CarrierConfigManager) mContext.getSystemService(Context 515 .CARRIER_CONFIG_SERVICE); 516 517 if (mConfigManager != null) { 518 PersistableBundle carrierConfig = mConfigManager.getConfigForSubId( 519 getSubId()); 520 521 522 if (carrierConfig != null) { 523 retryCountOverIms = carrierConfig.getInt( 524 CarrierConfigManager.ImsSms.KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT); 525 } 526 } 527 528 Rlog.d(TAG, "Retry Count Over Ims: " + retryCountOverIms); 529 530 return retryCountOverIms; 531 } 532 533 @Override getSmsRetryDelayValue()534 public int getSmsRetryDelayValue() { 535 int retryDelay = SEND_RETRY_DELAY; 536 CarrierConfigManager mConfigManager; 537 538 mConfigManager = (CarrierConfigManager) mContext.getSystemService( 539 Context.CARRIER_CONFIG_SERVICE); 540 541 if (mConfigManager != null) { 542 PersistableBundle carrierConfig = mConfigManager.getConfigForSubId( 543 getSubId()); 544 545 if (carrierConfig != null) { 546 retryDelay = carrierConfig.getInt( 547 CarrierConfigManager.ImsSms.KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT); 548 } 549 } 550 551 Rlog.d(TAG, "Retry delay: " + retryDelay); 552 553 return retryDelay; 554 } 555 556 @Override shouldBlockSmsForEcbm()557 protected boolean shouldBlockSmsForEcbm() { 558 // We should not block outgoing SMS during ECM on IMS. It only applies to outgoing CDMA 559 // SMS. 560 return false; 561 } 562 563 @Override getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader, int priority, int validityPeriod)564 protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr, 565 String message, boolean statusReportRequested, SmsHeader smsHeader, int priority, 566 int validityPeriod) { 567 return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, message, 568 statusReportRequested, smsHeader, priority, validityPeriod); 569 } 570 571 @Override getSubmitPdu(String scAddr, String destAddr, int destPort, byte[] message, boolean statusReportRequested)572 protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr, 573 int destPort, byte[] message, boolean statusReportRequested) { 574 return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, destPort, message, 575 statusReportRequested); 576 } 577 578 @Override getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader, int priority, int validityPeriod, int messageRef)579 protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr, 580 String message, boolean statusReportRequested, SmsHeader smsHeader, int priority, 581 int validityPeriod, int messageRef) { 582 return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, message, 583 statusReportRequested, smsHeader, priority, validityPeriod, messageRef); 584 } 585 586 @Override getSubmitPdu(String scAddr, String destAddr, int destPort, byte[] message, boolean statusReportRequested, int messageRef)587 protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr, 588 int destPort, byte[] message, boolean statusReportRequested, int messageRef) { 589 return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, destPort, message, 590 statusReportRequested, messageRef); 591 } 592 593 @Override calculateLength(CharSequence messageBody, boolean use7bitOnly)594 protected TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) { 595 return SMSDispatcherUtil.calculateLength(isCdmaMo(), messageBody, use7bitOnly); 596 } 597 598 /** 599 * Send the Memory Available Event to the ImsService 600 */ onMemoryAvailable()601 public void onMemoryAvailable() { 602 logd("onMemoryAvailable "); 603 int token = mNextToken.incrementAndGet(); 604 try { 605 logd("onMemoryAvailable: token = " + token); 606 mMemoryAvailableNotifierList.add(token); 607 getImsManager().onMemoryAvailable(token); 608 } catch (ImsException e) { 609 loge("onMemoryAvailable failed: " + e.getMessage()); 610 if (mMemoryAvailableNotifierList.contains(token)) { 611 mMemoryAvailableNotifierList.remove(Integer.valueOf(token)); 612 } 613 } 614 } 615 616 @Override sendSms(SmsTracker tracker)617 public void sendSms(SmsTracker tracker) { 618 logd("sendSms: " 619 + " mRetryCount=" + tracker.mRetryCount 620 + " mMessageRef=" + tracker.mMessageRef 621 + " SS=" + mPhone.getServiceState().getState()); 622 623 // Flag that this Tracker is using the ImsService implementation of SMS over IMS for sending 624 // this message. Any fallbacks will happen over CS only. 625 tracker.mUsesImsServiceForIms = true; 626 627 HashMap<String, Object> map = tracker.getData(); 628 629 byte[] pdu = (byte[]) map.get(MAP_KEY_PDU); 630 byte smsc[] = (byte[]) map.get(MAP_KEY_SMSC); 631 boolean isRetry = tracker.mRetryCount > 0; 632 String format = getFormat(); 633 634 if (SmsConstants.FORMAT_3GPP.equals(format) && isRetry) { 635 // per TS 23.040 Section 9.2.3.6: If TP-MTI SMS-SUBMIT (0x01) type 636 // TP-RD (bit 2) is 1 for retry 637 // and TP-MR is set to previously failed sms TP-MR 638 if (((0x01 & pdu[0]) == 0x01)) { 639 pdu[0] |= 0x04; // TP-RD 640 pdu[1] = (byte) tracker.mMessageRef; // TP-MR 641 } 642 } 643 644 int token = mNextToken.incrementAndGet(); 645 mTrackers.put(token, tracker); 646 try { 647 getImsManager().sendSms( 648 token, 649 tracker.mMessageRef, 650 format, 651 smsc != null ? IccUtils.bytesToHexString(smsc) : null, 652 isRetry, 653 pdu); 654 mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format, 655 ImsSmsImplBase.SEND_STATUS_OK, tracker.mMessageId); 656 } catch (ImsException e) { 657 loge("sendSms failed. Falling back to PSTN. Error: " + e.getMessage()); 658 mTrackers.remove(token); 659 fallbackToPstn(tracker); 660 mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format, 661 ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, tracker.mMessageId); 662 mPhone.getSmsStats().onOutgoingSms( 663 true /* isOverIms */, 664 SmsConstants.FORMAT_3GPP2.equals(format), 665 true /* fallbackToCs */, 666 SmsManager.RESULT_SYSTEM_ERROR, 667 tracker.mMessageId, 668 tracker.isFromDefaultSmsApplication(mContext), 669 tracker.getInterval(), 670 mTelephonyManager.isEmergencyNumber(tracker.mDestAddress)); 671 if (mPhone != null) { 672 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics(); 673 if (telephonyAnalytics != null) { 674 SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics(); 675 if (smsMmsAnalytics != null) { 676 smsMmsAnalytics.onOutgoingSms( 677 true /* isOverIms */, 678 SmsManager.RESULT_SYSTEM_ERROR 679 ); 680 } 681 } 682 } 683 } 684 } 685 getImsManager()686 private ImsManager getImsManager() throws ImsException { 687 synchronized (mLock) { 688 if (mImsManager == null) { 689 throw new ImsException("ImsManager not up", 690 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 691 } 692 return mImsManager; 693 } 694 } 695 696 @VisibleForTesting fallbackToPstn(SmsTracker tracker)697 public void fallbackToPstn(SmsTracker tracker) { 698 mSmsDispatchersController.sendRetrySms(tracker); 699 } 700 701 @Override isCdmaMo()702 protected boolean isCdmaMo() { 703 return mSmsDispatchersController.isCdmaFormat(getFormat()); 704 } 705 706 @VisibleForTesting getSmsListener()707 public IImsSmsListener getSmsListener() { 708 return mImsSmsListener; 709 } 710 logd(String s)711 private void logd(String s) { 712 Rlog.d(TAG + " [" + getPhoneId(mPhone) + "]", s); 713 } 714 logi(String s)715 private void logi(String s) { 716 Rlog.i(TAG + " [" + getPhoneId(mPhone) + "]", s); 717 } 718 logw(String s)719 private void logw(String s) { 720 Rlog.w(TAG + " [" + getPhoneId(mPhone) + "]", s); 721 } 722 loge(String s)723 private void loge(String s) { 724 Rlog.e(TAG + " [" + getPhoneId(mPhone) + "]", s); 725 } 726 getPhoneId(Phone phone)727 private static String getPhoneId(Phone phone) { 728 return (phone != null) ? Integer.toString(phone.getPhoneId()) : "?"; 729 } 730 } 731