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