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