1 /* 2 * Copyright (C) 2013 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.cdma; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.res.Resources; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.os.RemoteCallback; 27 import android.os.SystemProperties; 28 import android.provider.Telephony.Sms.Intents; 29 import android.telephony.PhoneNumberUtils; 30 import android.telephony.cdma.CdmaSmsCbProgramResults; 31 32 import com.android.internal.telephony.CommandsInterface; 33 import com.android.internal.telephony.InboundSmsHandler; 34 import com.android.internal.telephony.InboundSmsTracker; 35 import com.android.internal.telephony.Phone; 36 import com.android.internal.telephony.SmsConstants; 37 import com.android.internal.telephony.SmsMessageBase; 38 import com.android.internal.telephony.SmsStorageMonitor; 39 import com.android.internal.telephony.TelephonyComponentFactory; 40 import com.android.internal.telephony.WspTypeDecoder; 41 import com.android.internal.telephony.cdma.sms.BearerData; 42 import com.android.internal.telephony.cdma.sms.CdmaSmsAddress; 43 import com.android.internal.telephony.cdma.sms.SmsEnvelope; 44 import com.android.internal.util.HexDump; 45 46 import java.io.ByteArrayOutputStream; 47 import java.io.DataOutputStream; 48 import java.io.IOException; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 52 /** 53 * Subclass of {@link InboundSmsHandler} for 3GPP2 type messages. 54 */ 55 public class CdmaInboundSmsHandler extends InboundSmsHandler { 56 57 private final CdmaSMSDispatcher mSmsDispatcher; 58 private static CdmaCbTestBroadcastReceiver sTestBroadcastReceiver; 59 private static CdmaScpTestBroadcastReceiver sTestScpBroadcastReceiver; 60 61 private byte[] mLastDispatchedSmsFingerprint; 62 private byte[] mLastAcknowledgedSmsFingerprint; 63 64 // Callback used to process the result of an SCP message 65 private RemoteCallback mScpCallback; 66 67 private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean( 68 com.android.internal.R.bool.config_duplicate_port_omadm_wappush); 69 70 // When TEST_MODE is on we allow the test intent to trigger an SMS CB alert 71 private static final boolean TEST_MODE = SystemProperties.getInt("ro.debuggable", 0) == 1; 72 private static final String TEST_ACTION = "com.android.internal.telephony.cdma" 73 + ".TEST_TRIGGER_CELL_BROADCAST"; 74 private static final String SCP_TEST_ACTION = "com.android.internal.telephony.cdma" 75 + ".TEST_TRIGGER_SCP_MESSAGE"; 76 77 /** 78 * Create a new inbound SMS handler for CDMA. 79 */ CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher, Looper looper)80 private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor, 81 Phone phone, CdmaSMSDispatcher smsDispatcher, Looper looper) { 82 super("CdmaInboundSmsHandler", context, storageMonitor, phone, looper); 83 mSmsDispatcher = smsDispatcher; 84 phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null); 85 86 mCellBroadcastServiceManager.enable(); 87 mScpCallback = new RemoteCallback(result -> { 88 if (result == null) { 89 loge("SCP results error: missing extras"); 90 return; 91 } 92 String sender = result.getString("sender"); 93 if (sender == null) { 94 loge("SCP results error: missing sender extra."); 95 return; 96 } 97 ArrayList<CdmaSmsCbProgramResults> results = result.getParcelableArrayList("results"); 98 if (results == null) { 99 loge("SCP results error: missing results extra."); 100 return; 101 } 102 103 BearerData bData = new BearerData(); 104 bData.messageType = BearerData.MESSAGE_TYPE_SUBMIT; 105 bData.messageId = SmsMessage.getNextMessageId(); 106 bData.serviceCategoryProgramResults = results; 107 byte[] encodedBearerData = BearerData.encode(bData); 108 109 ByteArrayOutputStream baos = new ByteArrayOutputStream(100); 110 DataOutputStream dos = new DataOutputStream(baos); 111 try { 112 dos.writeInt(SmsEnvelope.TELESERVICE_SCPT); 113 dos.writeInt(0); //servicePresent 114 dos.writeInt(0); //serviceCategory 115 CdmaSmsAddress destAddr = CdmaSmsAddress.parse( 116 PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(sender)); 117 dos.write(destAddr.digitMode); 118 dos.write(destAddr.numberMode); 119 dos.write(destAddr.ton); // number_type 120 dos.write(destAddr.numberPlan); 121 dos.write(destAddr.numberOfDigits); 122 dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits 123 // Subaddress is not supported. 124 dos.write(0); //subaddressType 125 dos.write(0); //subaddr_odd 126 dos.write(0); //subaddr_nbr_of_digits 127 dos.write(encodedBearerData.length); 128 dos.write(encodedBearerData, 0, encodedBearerData.length); 129 // Ignore the RIL response. TODO: implement retry if SMS send fails. 130 mPhone.mCi.sendCdmaSms(baos.toByteArray(), null); 131 } catch (IOException e) { 132 loge("exception creating SCP results PDU", e); 133 } finally { 134 try { 135 dos.close(); 136 } catch (IOException ignored) { 137 } 138 } 139 }); 140 if (TEST_MODE) { 141 if (sTestBroadcastReceiver == null) { 142 sTestBroadcastReceiver = new CdmaCbTestBroadcastReceiver(); 143 IntentFilter filter = new IntentFilter(); 144 filter.addAction(TEST_ACTION); 145 context.registerReceiver(sTestBroadcastReceiver, filter, 146 Context.RECEIVER_EXPORTED); 147 } 148 if (sTestScpBroadcastReceiver == null) { 149 sTestScpBroadcastReceiver = new CdmaScpTestBroadcastReceiver(); 150 IntentFilter filter = new IntentFilter(); 151 filter.addAction(SCP_TEST_ACTION); 152 context.registerReceiver(sTestScpBroadcastReceiver, filter, 153 Context.RECEIVER_EXPORTED); 154 } 155 } 156 } 157 158 /** 159 * Unregister for CDMA SMS. 160 */ 161 @Override onQuitting()162 protected void onQuitting() { 163 mPhone.mCi.unSetOnNewCdmaSms(getHandler()); 164 165 if (DBG) log("unregistered for 3GPP2 SMS"); 166 super.onQuitting(); 167 } 168 169 /** 170 * Wait for state machine to enter startup state. We can't send any messages until then. 171 */ makeInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher, Looper looper)172 public static CdmaInboundSmsHandler makeInboundSmsHandler(Context context, 173 SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher, 174 Looper looper) { 175 CdmaInboundSmsHandler handler = new CdmaInboundSmsHandler(context, storageMonitor, 176 phone, smsDispatcher, looper); 177 handler.start(); 178 return handler; 179 } 180 181 /** 182 * Return true if this handler is for 3GPP2 messages; false for 3GPP format. 183 * 184 * @return true (3GPP2) 185 */ 186 @Override is3gpp2()187 protected boolean is3gpp2() { 188 return true; 189 } 190 191 /** 192 * Process Cell Broadcast, Voicemail Notification, and other 3GPP/3GPP2-specific messages. 193 * 194 * @param smsb the SmsMessageBase object from the RIL 195 * @param smsSource the source of the SMS message 196 * @return true if the message was handled here; false to continue processing 197 */ 198 @Override dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource, int token)199 protected int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource, 200 int token) { 201 SmsMessage sms = (SmsMessage) smsb; 202 boolean isBroadcastType = (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()); 203 204 // Handle CMAS emergency broadcast messages. 205 if (isBroadcastType) { 206 log("Broadcast type message"); 207 mCellBroadcastServiceManager.sendCdmaMessageToHandler(sms); 208 return Intents.RESULT_SMS_HANDLED; 209 } 210 211 // Initialize fingerprint field, and see if we have a network duplicate SMS. 212 mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint(); 213 if (mLastAcknowledgedSmsFingerprint != null && 214 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) { 215 return Intents.RESULT_SMS_HANDLED; 216 } 217 218 // Decode BD stream and set sms variables. 219 sms.parseSms(); 220 int teleService = sms.getTeleService(); 221 222 switch (teleService) { 223 case SmsEnvelope.TELESERVICE_VMN: 224 case SmsEnvelope.TELESERVICE_MWI: 225 // handle voicemail indication 226 handleVoicemailTeleservice(sms, smsSource); 227 return Intents.RESULT_SMS_HANDLED; 228 229 case SmsEnvelope.TELESERVICE_WMT: 230 case SmsEnvelope.TELESERVICE_WEMT: 231 if (sms.isStatusReportMessage()) { 232 mSmsDispatcher.sendStatusReportMessage(sms); 233 return Intents.RESULT_SMS_HANDLED; 234 } 235 break; 236 237 case SmsEnvelope.TELESERVICE_SCPT: 238 mCellBroadcastServiceManager.sendCdmaScpMessageToHandler(sms, mScpCallback); 239 return Intents.RESULT_SMS_HANDLED; 240 241 case SmsEnvelope.TELESERVICE_FDEA_WAP: 242 if (!sms.preprocessCdmaFdeaWap()) { 243 return Intents.RESULT_SMS_HANDLED; 244 } 245 teleService = SmsEnvelope.TELESERVICE_WAP; 246 // fall through 247 case SmsEnvelope.TELESERVICE_WAP: 248 // handled below, after storage check 249 break; 250 251 default: 252 loge("unsupported teleservice 0x" + Integer.toHexString(teleService)); 253 return Intents.RESULT_SMS_UNSUPPORTED; 254 } 255 256 if (!mStorageMonitor.isStorageAvailable() && 257 sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) { 258 // It's a storable message and there's no storage available. Bail. 259 // (See C.S0015-B v2.0 for a description of "Immediate Display" 260 // messages, which we represent as CLASS_0.) 261 return Intents.RESULT_SMS_OUT_OF_MEMORY; 262 } 263 264 if (SmsEnvelope.TELESERVICE_WAP == teleService) { 265 return processCdmaWapPdu(sms.getUserData(), sms.mMessageRef, 266 sms.getOriginatingAddress(), sms.getDisplayOriginatingAddress(), 267 sms.getTimestampMillis(), smsSource); 268 } 269 270 return dispatchNormalMessage(smsb, smsSource); 271 } 272 273 /** 274 * Send an acknowledge message. 275 * 276 * @param success indicates that last message was successfully received. 277 * @param result result code indicating any error 278 * @param response callback message sent when operation completes. 279 */ 280 @Override acknowledgeLastIncomingSms(boolean success, int result, Message response)281 protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) { 282 int causeCode = resultToCause(result); 283 mPhone.mCi.acknowledgeLastIncomingCdmaSms(success, causeCode, response); 284 285 if (causeCode == 0) { 286 mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint; 287 } 288 mLastDispatchedSmsFingerprint = null; 289 } 290 291 /** 292 * Convert Android result code to CDMA SMS failure cause. 293 * 294 * @param rc the Android SMS intent result value 295 * @return 0 for success, or a CDMA SMS failure cause value 296 */ resultToCause(int rc)297 private static int resultToCause(int rc) { 298 switch (rc) { 299 case Activity.RESULT_OK: 300 case Intents.RESULT_SMS_HANDLED: 301 // Cause code is ignored on success. 302 return 0; 303 case Intents.RESULT_SMS_OUT_OF_MEMORY: 304 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE; 305 case Intents.RESULT_SMS_UNSUPPORTED: 306 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID; 307 case Intents.RESULT_SMS_GENERIC_ERROR: 308 default: 309 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM; 310 } 311 } 312 313 /** 314 * Handle {@link SmsEnvelope#TELESERVICE_VMN} and {@link SmsEnvelope#TELESERVICE_MWI}. 315 * 316 * @param sms the message to process 317 */ handleVoicemailTeleservice(SmsMessage sms, @SmsSource int smsSource)318 private void handleVoicemailTeleservice(SmsMessage sms, @SmsSource int smsSource) { 319 int voicemailCount = sms.getNumOfVoicemails(); 320 if (DBG) log("Voicemail count=" + voicemailCount); 321 322 // range check 323 if (voicemailCount < 0) { 324 voicemailCount = -1; 325 } else if (voicemailCount > 99) { 326 // C.S0015-B v2, 4.5.12 327 // range: 0-99 328 voicemailCount = 99; 329 } 330 // update voice mail count in phone 331 mPhone.setVoiceMessageCount(voicemailCount); 332 // update metrics 333 addVoicemailSmsToMetrics(smsSource); 334 } 335 336 /** 337 * Processes inbound messages that are in the WAP-WDP PDU format. See 338 * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format. 339 * WDP segments are gathered until a datagram completes and gets dispatched. 340 * 341 * @param pdu The WAP-WDP PDU segment 342 * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or 343 * {@link Activity#RESULT_OK} if the message has been broadcast 344 * to applications 345 */ processCdmaWapPdu(byte[] pdu, int referenceNumber, String address, String dispAddr, long timestamp, @SmsSource int smsSource)346 private int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address, String dispAddr, 347 long timestamp, @SmsSource int smsSource) { 348 int index = 0; 349 350 int msgType = (0xFF & pdu[index++]); 351 if (msgType != 0) { 352 log("Received a WAP SMS which is not WDP. Discard."); 353 return Intents.RESULT_SMS_HANDLED; 354 } 355 int totalSegments = (0xFF & pdu[index++]); // >= 1 356 int segment = (0xFF & pdu[index++]); // >= 0 357 358 if (segment >= totalSegments) { 359 loge("WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1)); 360 return Intents.RESULT_SMS_HANDLED; 361 } 362 363 // Only the first segment contains sourcePort and destination Port 364 int sourcePort = 0; 365 int destinationPort = 0; 366 if (segment == 0) { 367 //process WDP segment 368 sourcePort = (0xFF & pdu[index++]) << 8; 369 sourcePort |= 0xFF & pdu[index++]; 370 destinationPort = (0xFF & pdu[index++]) << 8; 371 destinationPort |= 0xFF & pdu[index++]; 372 // Some carriers incorrectly send duplicate port fields in omadm wap pushes. 373 // If configured, check for that here 374 if (mCheckForDuplicatePortsInOmadmWapPush) { 375 if (checkDuplicatePortOmadmWapPush(pdu, index)) { 376 index = index + 4; // skip duplicate port fields 377 } 378 } 379 } 380 381 // Lookup all other related parts 382 log("Received WAP PDU. Type = " + msgType + ", originator = " + address 383 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort 384 + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments); 385 386 // pass the user data portion of the PDU to the shared handler in SMSDispatcher 387 byte[] userData = new byte[pdu.length - index]; 388 System.arraycopy(pdu, index, userData, 0, pdu.length - index); 389 InboundSmsTracker tracker = TelephonyComponentFactory.getInstance() 390 .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(mContext, 391 userData, timestamp, destinationPort, true, address, dispAddr, 392 referenceNumber, 393 segment, totalSegments, true, HexDump.toHexString(userData), 394 false /* isClass0 */, 395 mPhone.getSubId(), 396 smsSource); 397 398 // de-duping is done only for text messages 399 return addTrackerToRawTableAndSendMessage(tracker, false /* don't de-dup */); 400 } 401 402 /** 403 * Optional check to see if the received WapPush is an OMADM notification with erroneous 404 * extra port fields. 405 * - Some carriers make this mistake. 406 * ex: MSGTYPE-TotalSegments-CurrentSegment 407 * -SourcePortDestPort-SourcePortDestPort-OMADM PDU 408 * 409 * @param origPdu The WAP-WDP PDU segment 410 * @param index Current Index while parsing the PDU. 411 * @return True if OrigPdu is OmaDM Push Message which has duplicate ports. 412 * False if OrigPdu is NOT OmaDM Push Message which has duplicate ports. 413 */ checkDuplicatePortOmadmWapPush(byte[] origPdu, int index)414 private static boolean checkDuplicatePortOmadmWapPush(byte[] origPdu, int index) { 415 index += 4; 416 byte[] omaPdu = new byte[origPdu.length - index]; 417 System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length); 418 419 WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu); 420 int wspIndex = 2; 421 422 // Process header length field 423 if (!pduDecoder.decodeUintvarInteger(wspIndex)) { 424 return false; 425 } 426 427 wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field 428 429 // Process content type field 430 if (!pduDecoder.decodeContentType(wspIndex)) { 431 return false; 432 } 433 434 String mimeType = pduDecoder.getValueString(); 435 return (WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI.equals(mimeType)); 436 } 437 438 /** 439 * Add voicemail indication SMS 0 to metrics. 440 */ addVoicemailSmsToMetrics(@msSource int smsSource)441 private void addVoicemailSmsToMetrics(@SmsSource int smsSource) { 442 mMetrics.writeIncomingVoiceMailSms(mPhone.getPhoneId(), 443 android.telephony.SmsMessage.FORMAT_3GPP2); 444 mPhone.getSmsStats().onIncomingSmsVoicemail(true /* is3gpp2 */, smsSource); 445 } 446 447 /** 448 * A broadcast receiver used for testing emergency cell broadcasts. To trigger test CDMA cell 449 * broadcasts with adb run e.g: 450 * 451 * adb shell am broadcast -a com.android.internal.telephony.cdma.TEST_TRIGGER_CELL_BROADCAST \ 452 * --ei service_category 4097 \ 453 * --es bearer_data_string 0003104D200801C00D010101510278000260A34C834E4208D327CF8882BC1A53A \ 454 * 4CE8E8234FA4829CFAB52420873E1CF9D2674F410E7D59D52CA05A8274FA5524208716754A506620834A4DA9F \ 455 * 3A0A0AB3AA499881A316A8284D41369D40 456 * 457 * adb shell am broadcast -a com.android.internal.telephony.cdma.TEST_TRIGGER_CELL_BROADCAST \ 458 * --ei service_category 4097 \ 459 * --es bearer_data_string 0003104D200801C00D010101510278000260A34C834E4208D327CF8882BC1A53A \ 460 * 4CE8E8234FA4829CFAB52420873E1CF9D2674F410E7D59D52CA05A8274FA5524208716754A506620834A4DA9F \ 461 * 3A0A0AB3AA499881A316A8284D41369D40 \ 462 * --ei phone_id 0 463 */ 464 private class CdmaCbTestBroadcastReceiver extends CbTestBroadcastReceiver { 465 CdmaCbTestBroadcastReceiver()466 CdmaCbTestBroadcastReceiver() { 467 super(TEST_ACTION); 468 } 469 470 @Override handleTestAction(Intent intent)471 protected void handleTestAction(Intent intent) { 472 SmsEnvelope envelope = new SmsEnvelope(); 473 // the CdmaSmsAddress is not used for a test cell broadcast message, but needs to be 474 // supplied to avoid a null pointer exception in the platform 475 CdmaSmsAddress nonNullAddress = new CdmaSmsAddress(); 476 nonNullAddress.origBytes = new byte[]{(byte) 0xFF}; 477 envelope.origAddress = nonNullAddress; 478 479 // parse service category from intent 480 envelope.serviceCategory = intent.getIntExtra("service_category", -1); 481 if (envelope.serviceCategory == -1) { 482 log("No service category, ignoring CB test intent"); 483 return; 484 } 485 486 // parse bearer data from intent 487 String bearerDataString = intent.getStringExtra("bearer_data_string"); 488 envelope.bearerData = decodeHexString(bearerDataString); 489 if (envelope.bearerData == null) { 490 log("No bearer data, ignoring CB test intent"); 491 return; 492 } 493 494 SmsMessage sms = new SmsMessage(new CdmaSmsAddress(), envelope); 495 mCellBroadcastServiceManager.sendCdmaMessageToHandler(sms); 496 } 497 } 498 499 /** 500 * A broadcast receiver used for testing CDMA SCP messages. To trigger test CDMA SCP messages 501 * with adb run e.g: 502 * 503 * adb shell am broadcast -a com.android.internal.telephony.cdma.TEST_TRIGGER_SCP_MESSAGE \ 504 * --es originating_address_string 1234567890 \ 505 * --es bearer_data_string 00031007B0122610880080B2091C5F1D3965DB95054D1CB2E1E883A6F41334E \ 506 * 6CA830EEC882872DFC32F2E9E40 507 */ 508 private class CdmaScpTestBroadcastReceiver extends CbTestBroadcastReceiver { 509 CdmaScpTestBroadcastReceiver()510 CdmaScpTestBroadcastReceiver() { 511 super(SCP_TEST_ACTION); 512 } 513 514 @Override handleTestAction(Intent intent)515 protected void handleTestAction(Intent intent) { 516 SmsEnvelope envelope = new SmsEnvelope(); 517 // the CdmaSmsAddress is not used for a test SCP message, but needs to be supplied to 518 // avoid a null pointer exception in the platform 519 CdmaSmsAddress nonNullAddress = new CdmaSmsAddress(); 520 nonNullAddress.origBytes = new byte[]{(byte) 0xFF}; 521 envelope.origAddress = nonNullAddress; 522 523 // parse bearer data from intent 524 String bearerDataString = intent.getStringExtra("bearer_data_string"); 525 envelope.bearerData = decodeHexString(bearerDataString); 526 if (envelope.bearerData == null) { 527 log("No bearer data, ignoring SCP test intent"); 528 return; 529 } 530 531 CdmaSmsAddress origAddr = new CdmaSmsAddress(); 532 String addressString = intent.getStringExtra("originating_address_string"); 533 origAddr.origBytes = decodeHexString(addressString); 534 if (origAddr.origBytes == null) { 535 log("No address data, ignoring SCP test intent"); 536 return; 537 } 538 SmsMessage sms = new SmsMessage(origAddr, envelope); 539 sms.parseSms(); 540 mCellBroadcastServiceManager.sendCdmaScpMessageToHandler(sms, mScpCallback); 541 } 542 } 543 } 544