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.content.Context; 20 import android.os.Binder; 21 import android.os.Message; 22 import android.os.PersistableBundle; 23 import android.os.RemoteException; 24 import android.provider.Telephony.Sms.Intents; 25 import android.telephony.CarrierConfigManager; 26 import android.telephony.ServiceState; 27 import android.telephony.SmsManager; 28 import android.telephony.ims.ImsReasonInfo; 29 import android.telephony.ims.RegistrationManager; 30 import android.telephony.ims.aidl.IImsSmsListener; 31 import android.telephony.ims.feature.MmTelFeature; 32 import android.telephony.ims.stub.ImsRegistrationImplBase; 33 import android.telephony.ims.stub.ImsSmsImplBase; 34 import android.telephony.ims.stub.ImsSmsImplBase.SendStatusResult; 35 36 import com.android.ims.FeatureConnector; 37 import com.android.ims.ImsException; 38 import com.android.ims.ImsManager; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; 41 import com.android.internal.telephony.metrics.TelephonyMetrics; 42 import com.android.internal.telephony.uicc.IccUtils; 43 import com.android.internal.telephony.util.SMSDispatcherUtil; 44 import com.android.telephony.Rlog; 45 46 import java.util.HashMap; 47 import java.util.Map; 48 import java.util.concurrent.ConcurrentHashMap; 49 import java.util.concurrent.Executor; 50 import java.util.concurrent.atomic.AtomicInteger; 51 52 /** 53 * Responsible for communications with {@link com.android.ims.ImsManager} to send/receive messages 54 * over IMS. 55 * @hide 56 */ 57 public class ImsSmsDispatcher extends SMSDispatcher { 58 59 private static final String TAG = "ImsSmsDispatcher"; 60 private static final int CONNECT_DELAY_MS = 5000; // 5 seconds; 61 62 /** 63 * Creates FeatureConnector instances for ImsManager, used during testing to inject mock 64 * connector instances. 65 */ 66 @VisibleForTesting 67 public interface FeatureConnectorFactory { 68 /** 69 * Create a new FeatureConnector for ImsManager. 70 */ create(Context context, int phoneId, String logPrefix, FeatureConnector.Listener<ImsManager> listener, Executor executor)71 FeatureConnector<ImsManager> create(Context context, int phoneId, String logPrefix, 72 FeatureConnector.Listener<ImsManager> listener, Executor executor); 73 } 74 75 @VisibleForTesting 76 public Map<Integer, SmsTracker> mTrackers = new ConcurrentHashMap<>(); 77 @VisibleForTesting 78 public AtomicInteger mNextToken = new AtomicInteger(); 79 private final Object mLock = new Object(); 80 private volatile boolean mIsSmsCapable; 81 private volatile boolean mIsImsServiceUp; 82 private volatile boolean mIsRegistered; 83 private final FeatureConnector<ImsManager> mImsManagerConnector; 84 /** Telephony metrics instance for logging metrics event */ 85 private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance(); 86 private ImsManager mImsManager; 87 private FeatureConnectorFactory mConnectorFactory; 88 89 private Runnable mConnectRunnable = new Runnable() { 90 @Override 91 public void run() { 92 mImsManagerConnector.connect(); 93 } 94 }; 95 96 /** 97 * Listen to the IMS service state change 98 * 99 */ 100 private RegistrationManager.RegistrationCallback mRegistrationCallback = 101 new RegistrationManager.RegistrationCallback() { 102 @Override 103 public void onRegistered( 104 @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) { 105 logd("onImsConnected imsRadioTech=" + imsRadioTech); 106 synchronized (mLock) { 107 mIsRegistered = true; 108 } 109 } 110 111 @Override 112 public void onRegistering( 113 @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) { 114 logd("onImsProgressing imsRadioTech=" + imsRadioTech); 115 synchronized (mLock) { 116 mIsRegistered = false; 117 } 118 } 119 120 @Override 121 public void onUnregistered(ImsReasonInfo info) { 122 logd("onImsDisconnected imsReasonInfo=" + info); 123 synchronized (mLock) { 124 mIsRegistered = false; 125 } 126 } 127 }; 128 129 private android.telephony.ims.ImsMmTelManager.CapabilityCallback mCapabilityCallback = 130 new android.telephony.ims.ImsMmTelManager.CapabilityCallback() { 131 @Override 132 public void onCapabilitiesStatusChanged( 133 MmTelFeature.MmTelCapabilities capabilities) { 134 synchronized (mLock) { 135 mIsSmsCapable = capabilities.isCapable( 136 MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS); 137 } 138 } 139 }; 140 141 private final IImsSmsListener mImsSmsListener = new IImsSmsListener.Stub() { 142 @Override 143 public void onSendSmsResult(int token, int messageRef, @SendStatusResult int status, 144 @SmsManager.Result int reason, int networkReasonCode) { 145 final long identity = Binder.clearCallingIdentity(); 146 try { 147 logd("onSendSmsResult token=" + token + " messageRef=" + messageRef 148 + " status=" + status + " reason=" + reason + " networkReasonCode=" 149 + networkReasonCode); 150 // TODO integrate networkReasonCode into IMS SMS metrics. 151 SmsTracker tracker = mTrackers.get(token); 152 mMetrics.writeOnImsServiceSmsSolicitedResponse(mPhone.getPhoneId(), status, reason, 153 (tracker != null ? tracker.mMessageId : 0L)); 154 if (tracker == null) { 155 throw new IllegalArgumentException("Invalid token."); 156 } 157 tracker.mMessageRef = messageRef; 158 switch(status) { 159 case ImsSmsImplBase.SEND_STATUS_OK: 160 if (tracker.mDeliveryIntent != null) { 161 // Expecting a status report. Put this tracker to the map. 162 mSmsDispatchersController.putDeliveryPendingTracker(tracker); 163 } 164 tracker.onSent(mContext); 165 mTrackers.remove(token); 166 mPhone.notifySmsSent(tracker.mDestAddress); 167 break; 168 case ImsSmsImplBase.SEND_STATUS_ERROR: 169 tracker.onFailed(mContext, reason, networkReasonCode); 170 mTrackers.remove(token); 171 break; 172 case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY: 173 if (tracker.mRetryCount < MAX_SEND_RETRIES) { 174 tracker.mRetryCount += 1; 175 sendMessageDelayed( 176 obtainMessage(EVENT_SEND_RETRY, tracker), SEND_RETRY_DELAY); 177 } else { 178 tracker.onFailed(mContext, reason, networkReasonCode); 179 mTrackers.remove(token); 180 } 181 break; 182 case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK: 183 // Skip MAX_SEND_RETRIES checking here. It allows CSFB after 184 // SEND_STATUS_ERROR_RETRY up to MAX_SEND_RETRIES even. 185 tracker.mRetryCount += 1; 186 mTrackers.remove(token); 187 fallbackToPstn(tracker); 188 break; 189 default: 190 } 191 mPhone.getSmsStats().onOutgoingSms( 192 true /* isOverIms */, 193 SmsConstants.FORMAT_3GPP2.equals(getFormat()), 194 status == ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, 195 reason, 196 tracker.mMessageId, 197 tracker.isFromDefaultSmsApplication(mContext), 198 tracker.getInterval()); 199 } finally { 200 Binder.restoreCallingIdentity(identity); 201 } 202 } 203 204 @Override 205 public void onSmsStatusReportReceived(int token, String format, byte[] pdu) 206 throws RemoteException { 207 final long identity = Binder.clearCallingIdentity(); 208 try { 209 logd("Status report received."); 210 android.telephony.SmsMessage message = 211 android.telephony.SmsMessage.createFromPdu(pdu, format); 212 if (message == null || message.mWrappedSmsMessage == null) { 213 throw new RemoteException( 214 "Status report received with a PDU that could not be parsed."); 215 } 216 mSmsDispatchersController.handleSmsStatusReport(format, pdu); 217 try { 218 getImsManager().acknowledgeSmsReport( 219 token, 220 message.mWrappedSmsMessage.mMessageRef, 221 ImsSmsImplBase.STATUS_REPORT_STATUS_OK); 222 } catch (ImsException e) { 223 loge("Failed to acknowledgeSmsReport(). Error: " + e.getMessage()); 224 } 225 } finally { 226 Binder.restoreCallingIdentity(identity); 227 } 228 } 229 230 @Override 231 public void onSmsReceived(int token, String format, byte[] pdu) { 232 final long identity = Binder.clearCallingIdentity(); 233 try { 234 logd("SMS received."); 235 android.telephony.SmsMessage message = 236 android.telephony.SmsMessage.createFromPdu(pdu, format); 237 mSmsDispatchersController.injectSmsPdu(message, format, result -> { 238 logd("SMS handled result: " + result); 239 int mappedResult; 240 switch (result) { 241 case Intents.RESULT_SMS_HANDLED: 242 mappedResult = ImsSmsImplBase.DELIVER_STATUS_OK; 243 break; 244 case Intents.RESULT_SMS_OUT_OF_MEMORY: 245 mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_NO_MEMORY; 246 break; 247 case Intents.RESULT_SMS_UNSUPPORTED: 248 mappedResult = 249 ImsSmsImplBase.DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED; 250 break; 251 default: 252 mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC; 253 break; 254 } 255 try { 256 if (message != null && message.mWrappedSmsMessage != null) { 257 getImsManager().acknowledgeSms(token, 258 message.mWrappedSmsMessage.mMessageRef, mappedResult); 259 } else { 260 logw("SMS Received with a PDU that could not be parsed."); 261 getImsManager().acknowledgeSms(token, 0, mappedResult); 262 } 263 } catch (ImsException e) { 264 loge("Failed to acknowledgeSms(). Error: " + e.getMessage()); 265 } 266 }, true /* ignoreClass */, true /* isOverIms */); 267 } finally { 268 Binder.restoreCallingIdentity(identity); 269 } 270 } 271 }; 272 273 @Override handleMessage(Message msg)274 public void handleMessage(Message msg) { 275 switch (msg.what) { 276 case EVENT_SEND_RETRY: 277 logd("SMS retry.."); 278 sendSms((SmsTracker) msg.obj); 279 break; 280 default: 281 super.handleMessage(msg); 282 } 283 } 284 ImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController, FeatureConnectorFactory factory)285 public ImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController, 286 FeatureConnectorFactory factory) { 287 super(phone, smsDispatchersController); 288 mConnectorFactory = factory; 289 290 mImsManagerConnector = mConnectorFactory.create(mContext, mPhone.getPhoneId(), TAG, 291 new FeatureConnector.Listener<ImsManager>() { 292 public void connectionReady(ImsManager manager, int subId) throws ImsException { 293 logd("ImsManager: connection ready."); 294 synchronized (mLock) { 295 mImsManager = manager; 296 setListeners(); 297 mIsImsServiceUp = true; 298 } 299 } 300 301 @Override 302 public void connectionUnavailable(int reason) { 303 logd("ImsManager: connection unavailable, reason=" + reason); 304 if (reason == FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE) { 305 loge("connectionUnavailable: unexpected, received server error"); 306 removeCallbacks(mConnectRunnable); 307 postDelayed(mConnectRunnable, CONNECT_DELAY_MS); 308 } 309 synchronized (mLock) { 310 mImsManager = null; 311 mIsImsServiceUp = false; 312 } 313 } 314 }, this::post); 315 post(mConnectRunnable); 316 } 317 setListeners()318 private void setListeners() throws ImsException { 319 getImsManager().addRegistrationCallback(mRegistrationCallback, this::post); 320 getImsManager().addCapabilitiesCallback(mCapabilityCallback, this::post); 321 getImsManager().setSmsListener(getSmsListener()); 322 getImsManager().onSmsReady(); 323 } 324 isLteService()325 private boolean isLteService() { 326 return ((mPhone.getServiceState().getRilDataRadioTechnology() == 327 ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && (mPhone.getServiceState(). 328 getDataRegistrationState() == ServiceState.STATE_IN_SERVICE)); 329 } 330 isLimitedLteService()331 private boolean isLimitedLteService() { 332 return ((mPhone.getServiceState().getRilVoiceRadioTechnology() == 333 ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && mPhone.getServiceState().isEmergencyOnly()); 334 } 335 isEmergencySmsPossible()336 private boolean isEmergencySmsPossible() { 337 return isLteService() || isLimitedLteService(); 338 } 339 isEmergencySmsSupport(String destAddr)340 public boolean isEmergencySmsSupport(String destAddr) { 341 PersistableBundle b; 342 boolean eSmsCarrierSupport = false; 343 if (!mTelephonyManager.isEmergencyNumber(destAddr)) { 344 logi(Rlog.pii(TAG, destAddr) + " is not emergency number"); 345 return false; 346 } 347 348 final long identity = Binder.clearCallingIdentity(); 349 try { 350 CarrierConfigManager configManager = (CarrierConfigManager) mContext 351 .getSystemService(Context.CARRIER_CONFIG_SERVICE); 352 if (configManager == null) { 353 loge("configManager is null"); 354 return false; 355 } 356 b = configManager.getConfigForSubId(getSubId()); 357 if (b == null) { 358 loge("PersistableBundle is null"); 359 return false; 360 } 361 eSmsCarrierSupport = b.getBoolean( 362 CarrierConfigManager.KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL); 363 boolean lteOrLimitedLte = isEmergencySmsPossible(); 364 logi("isEmergencySmsSupport emergencySmsCarrierSupport: " 365 + eSmsCarrierSupport + " destAddr: " + Rlog.pii(TAG, destAddr) 366 + " mIsImsServiceUp: " + mIsImsServiceUp + " lteOrLimitedLte: " 367 + lteOrLimitedLte); 368 369 return eSmsCarrierSupport && mIsImsServiceUp && lteOrLimitedLte; 370 } finally { 371 Binder.restoreCallingIdentity(identity); 372 } 373 } 374 isAvailable()375 public boolean isAvailable() { 376 synchronized (mLock) { 377 logd("isAvailable: up=" + mIsImsServiceUp + ", reg= " + mIsRegistered 378 + ", cap= " + mIsSmsCapable); 379 return mIsImsServiceUp && mIsRegistered && mIsSmsCapable; 380 } 381 } 382 383 @Override getFormat()384 protected String getFormat() { 385 // This is called in the constructor before ImsSmsDispatcher has a chance to initialize 386 // mLock. ImsManager will not be up anyway at this point, so report UNKNOWN. 387 if (mLock == null) return SmsConstants.FORMAT_UNKNOWN; 388 try { 389 return getImsManager().getSmsFormat(); 390 } catch (ImsException e) { 391 loge("Failed to get sms format. Error: " + e.getMessage()); 392 return SmsConstants.FORMAT_UNKNOWN; 393 } 394 } 395 396 @Override shouldBlockSmsForEcbm()397 protected boolean shouldBlockSmsForEcbm() { 398 // We should not block outgoing SMS during ECM on IMS. It only applies to outgoing CDMA 399 // SMS. 400 return false; 401 } 402 403 @Override getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader, int priority, int validityPeriod)404 protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr, 405 String message, boolean statusReportRequested, SmsHeader smsHeader, int priority, 406 int validityPeriod) { 407 return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, message, 408 statusReportRequested, smsHeader, priority, validityPeriod); 409 } 410 411 @Override getSubmitPdu(String scAddr, String destAddr, int destPort, byte[] message, boolean statusReportRequested)412 protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr, 413 int destPort, byte[] message, boolean statusReportRequested) { 414 return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, destPort, message, 415 statusReportRequested); 416 } 417 418 @Override calculateLength(CharSequence messageBody, boolean use7bitOnly)419 protected TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) { 420 return SMSDispatcherUtil.calculateLength(isCdmaMo(), messageBody, use7bitOnly); 421 } 422 423 @Override sendSms(SmsTracker tracker)424 public void sendSms(SmsTracker tracker) { 425 logd("sendSms: " 426 + " mRetryCount=" + tracker.mRetryCount 427 + " mMessageRef=" + tracker.mMessageRef 428 + " SS=" + mPhone.getServiceState().getState()); 429 430 // Flag that this Tracker is using the ImsService implementation of SMS over IMS for sending 431 // this message. Any fallbacks will happen over CS only. 432 tracker.mUsesImsServiceForIms = true; 433 434 HashMap<String, Object> map = tracker.getData(); 435 436 byte[] pdu = (byte[]) map.get(MAP_KEY_PDU); 437 byte smsc[] = (byte[]) map.get(MAP_KEY_SMSC); 438 boolean isRetry = tracker.mRetryCount > 0; 439 String format = getFormat(); 440 441 if (SmsConstants.FORMAT_3GPP.equals(format) && isRetry) { 442 // per TS 23.040 Section 9.2.3.6: If TP-MTI SMS-SUBMIT (0x01) type 443 // TP-RD (bit 2) is 1 for retry 444 // and TP-MR is set to previously failed sms TP-MR 445 if (((0x01 & pdu[0]) == 0x01)) { 446 pdu[0] |= 0x04; // TP-RD 447 pdu[1] = (byte) tracker.mMessageRef; // TP-MR 448 } 449 } 450 451 int token = mNextToken.incrementAndGet(); 452 mTrackers.put(token, tracker); 453 try { 454 getImsManager().sendSms( 455 token, 456 tracker.mMessageRef, 457 format, 458 smsc != null ? IccUtils.bytesToHexString(smsc) : null, 459 isRetry, 460 pdu); 461 mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format, 462 ImsSmsImplBase.SEND_STATUS_OK, tracker.mMessageId); 463 } catch (ImsException e) { 464 loge("sendSms failed. Falling back to PSTN. Error: " + e.getMessage()); 465 mTrackers.remove(token); 466 fallbackToPstn(tracker); 467 mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format, 468 ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, tracker.mMessageId); 469 mPhone.getSmsStats().onOutgoingSms( 470 true /* isOverIms */, 471 SmsConstants.FORMAT_3GPP2.equals(format), 472 true /* fallbackToCs */, 473 SmsManager.RESULT_SYSTEM_ERROR, 474 tracker.mMessageId, 475 tracker.isFromDefaultSmsApplication(mContext), 476 tracker.getInterval()); 477 } 478 } 479 getImsManager()480 private ImsManager getImsManager() throws ImsException { 481 synchronized (mLock) { 482 if (mImsManager == null) { 483 throw new ImsException("ImsManager not up", 484 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 485 } 486 return mImsManager; 487 } 488 } 489 490 @VisibleForTesting fallbackToPstn(SmsTracker tracker)491 public void fallbackToPstn(SmsTracker tracker) { 492 mSmsDispatchersController.sendRetrySms(tracker); 493 } 494 495 @Override isCdmaMo()496 protected boolean isCdmaMo() { 497 return mSmsDispatchersController.isCdmaFormat(getFormat()); 498 } 499 500 @VisibleForTesting getSmsListener()501 public IImsSmsListener getSmsListener() { 502 return mImsSmsListener; 503 } 504 logd(String s)505 private void logd(String s) { 506 Rlog.d(TAG + " [" + getPhoneId(mPhone) + "]", s); 507 } 508 logi(String s)509 private void logi(String s) { 510 Rlog.i(TAG + " [" + getPhoneId(mPhone) + "]", s); 511 } 512 logw(String s)513 private void logw(String s) { 514 Rlog.w(TAG + " [" + getPhoneId(mPhone) + "]", s); 515 } 516 loge(String s)517 private void loge(String s) { 518 Rlog.e(TAG + " [" + getPhoneId(mPhone) + "]", s); 519 } 520 getPhoneId(Phone phone)521 private static String getPhoneId(Phone phone) { 522 return (phone != null) ? Integer.toString(phone.getPhoneId()) : "?"; 523 } 524 } 525