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