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