CdmaSMSDispatcher.java revision 850665a367489cce0b83431fa0e6e543b24062e0
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * Copyright (c) 2012, The Linux Foundation. All rights reserved. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.internal.telephony.cdma; 19 20 21import android.app.Activity; 22import android.app.AppOpsManager; 23import android.app.PendingIntent; 24import android.app.PendingIntent.CanceledException; 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.Intent; 28import android.content.SharedPreferences; 29import android.content.res.Resources; 30import android.os.Bundle; 31import android.os.Message; 32import android.os.SystemProperties; 33import android.preference.PreferenceManager; 34import android.provider.Telephony.Sms.Intents; 35import android.telephony.PhoneNumberUtils; 36import android.telephony.SmsCbMessage; 37import android.telephony.SmsManager; 38import android.telephony.cdma.CdmaSmsCbProgramData; 39import android.telephony.cdma.CdmaSmsCbProgramResults; 40import android.telephony.Rlog; 41 42import com.android.internal.telephony.CommandsInterface; 43import com.android.internal.telephony.GsmAlphabet; 44import com.android.internal.telephony.SmsConstants; 45import com.android.internal.telephony.PhoneBase; 46import com.android.internal.telephony.SMSDispatcher; 47import com.android.internal.telephony.ImsSMSDispatcher; 48import com.android.internal.telephony.SmsHeader; 49import com.android.internal.telephony.SmsMessageBase; 50import com.android.internal.telephony.SmsStorageMonitor; 51import com.android.internal.telephony.SmsUsageMonitor; 52import com.android.internal.telephony.TelephonyProperties; 53import com.android.internal.telephony.WspTypeDecoder; 54import com.android.internal.telephony.cdma.sms.BearerData; 55import com.android.internal.telephony.cdma.sms.CdmaSmsAddress; 56import com.android.internal.telephony.cdma.sms.SmsEnvelope; 57import com.android.internal.telephony.cdma.sms.UserData; 58 59import java.io.ByteArrayOutputStream; 60import java.io.DataOutputStream; 61import java.io.IOException; 62import java.util.ArrayList; 63import java.util.Arrays; 64import java.util.HashMap; 65 66 67public class CdmaSMSDispatcher extends SMSDispatcher { 68 private static final String TAG = "CdmaSMSDispatcher"; 69 private static final boolean VDBG = false; 70 private ImsSMSDispatcher mImsSMSDispatcher; 71 72 private byte[] mLastDispatchedSmsFingerprint; 73 private byte[] mLastAcknowledgedSmsFingerprint; 74 75 private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean( 76 com.android.internal.R.bool.config_duplicate_port_omadm_wappush); 77 78 public CdmaSMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor, 79 SmsUsageMonitor usageMonitor, ImsSMSDispatcher imsSMSDispatcher) { 80 super(phone, storageMonitor, usageMonitor); 81 mCi.setOnNewCdmaSms(this, EVENT_NEW_SMS, null); 82 mImsSMSDispatcher = imsSMSDispatcher; 83 Rlog.d(TAG, "CdmaSMSDispatcher created"); 84 } 85 86 @Override 87 public void dispose() { 88 mCi.unSetOnNewCdmaSms(this); 89 } 90 91 @Override 92 protected String getFormat() { 93 return SmsConstants.FORMAT_3GPP2; 94 } 95 96 private void handleCdmaStatusReport(SmsMessage sms) { 97 for (int i = 0, count = deliveryPendingList.size(); i < count; i++) { 98 SmsTracker tracker = deliveryPendingList.get(i); 99 if (tracker.mMessageRef == sms.mMessageRef) { 100 // Found it. Remove from list and broadcast. 101 deliveryPendingList.remove(i); 102 PendingIntent intent = tracker.mDeliveryIntent; 103 Intent fillIn = new Intent(); 104 fillIn.putExtra("pdu", sms.getPdu()); 105 fillIn.putExtra("format", getFormat()); 106 try { 107 intent.send(mContext, Activity.RESULT_OK, fillIn); 108 } catch (CanceledException ex) {} 109 break; // Only expect to see one tracker matching this message. 110 } 111 } 112 } 113 114 /** 115 * Dispatch service category program data to the CellBroadcastReceiver app, which filters 116 * the broadcast alerts to display. 117 * @param sms the SMS message containing one or more 118 * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects. 119 */ 120 private void handleServiceCategoryProgramData(SmsMessage sms) { 121 ArrayList<CdmaSmsCbProgramData> programDataList = sms.getSmsCbProgramData(); 122 if (programDataList == null) { 123 Rlog.e(TAG, "handleServiceCategoryProgramData: program data list is null!"); 124 return; 125 } 126 127 Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION); 128 intent.putExtra("sender", sms.getOriginatingAddress()); 129 intent.putParcelableArrayListExtra("program_data", programDataList); 130 dispatch(intent, RECEIVE_SMS_PERMISSION, AppOpsManager.OP_RECEIVE_SMS, mScpResultsReceiver); 131 } 132 133 /** {@inheritDoc} */ 134 @Override 135 protected int dispatchMessage(SmsMessageBase smsb) { 136 137 // If sms is null, means there was a parsing error. 138 if (smsb == null) { 139 Rlog.e(TAG, "dispatchMessage: message is null"); 140 return Intents.RESULT_SMS_GENERIC_ERROR; 141 } 142 143 String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false"); 144 if (inEcm.equals("true")) { 145 return Activity.RESULT_OK; 146 } 147 148 if (mSmsReceiveDisabled) { 149 // Device doesn't support receiving SMS, 150 Rlog.d(TAG, "Received short message on device which doesn't support " 151 + "receiving SMS. Ignored."); 152 return Intents.RESULT_SMS_HANDLED; 153 } 154 155 SmsMessage sms = (SmsMessage) smsb; 156 157 // Handle CMAS emergency broadcast messages. 158 if (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()) { 159 Rlog.d(TAG, "Broadcast type message"); 160 SmsCbMessage message = sms.parseBroadcastSms(); 161 if (message != null) { 162 dispatchBroadcastMessage(message); 163 } 164 return Intents.RESULT_SMS_HANDLED; 165 } 166 167 // See if we have a network duplicate SMS. 168 mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint(); 169 if (mLastAcknowledgedSmsFingerprint != null && 170 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) { 171 return Intents.RESULT_SMS_HANDLED; 172 } 173 // Decode BD stream and set sms variables. 174 sms.parseSms(); 175 int teleService = sms.getTeleService(); 176 boolean handled = false; 177 178 if ((SmsEnvelope.TELESERVICE_VMN == teleService) || 179 (SmsEnvelope.TELESERVICE_MWI == teleService)) { 180 // handling Voicemail 181 int voicemailCount = sms.getNumOfVoicemails(); 182 Rlog.d(TAG, "Voicemail count=" + voicemailCount); 183 // Store the voicemail count in preferences. 184 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( 185 mContext); 186 SharedPreferences.Editor editor = sp.edit(); 187 editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount); 188 editor.apply(); 189 mPhone.setVoiceMessageWaiting(1, voicemailCount); 190 handled = true; 191 } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) || 192 (SmsEnvelope.TELESERVICE_WEMT == teleService)) && 193 sms.isStatusReportMessage()) { 194 handleCdmaStatusReport(sms); 195 handled = true; 196 } else if (SmsEnvelope.TELESERVICE_SCPT == teleService) { 197 handleServiceCategoryProgramData(sms); 198 handled = true; 199 } else if ((sms.getUserData() == null)) { 200 if (VDBG) { 201 Rlog.d(TAG, "Received SMS without user data"); 202 } 203 handled = true; 204 } 205 206 if (handled) { 207 return Intents.RESULT_SMS_HANDLED; 208 } 209 210 if (!mStorageMonitor.isStorageAvailable() && 211 sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) { 212 // It's a storable message and there's no storage available. Bail. 213 // (See C.S0015-B v2.0 for a description of "Immediate Display" 214 // messages, which we represent as CLASS_0.) 215 return Intents.RESULT_SMS_OUT_OF_MEMORY; 216 } 217 218 if (SmsEnvelope.TELESERVICE_WAP == teleService) { 219 return processCdmaWapPdu(sms.getUserData(), sms.mMessageRef, 220 sms.getOriginatingAddress()); 221 } 222 223 // Reject (NAK) any messages with teleservice ids that have 224 // not yet been handled and also do not correspond to the two 225 // kinds that are processed below. 226 if ((SmsEnvelope.TELESERVICE_WMT != teleService) && 227 (SmsEnvelope.TELESERVICE_WEMT != teleService) && 228 (SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) { 229 return Intents.RESULT_SMS_UNSUPPORTED; 230 } 231 232 return dispatchNormalMessage(smsb); 233 } 234 235 /** 236 * Processes inbound messages that are in the WAP-WDP PDU format. See 237 * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format. 238 * WDP segments are gathered until a datagram completes and gets dispatched. 239 * 240 * @param pdu The WAP-WDP PDU segment 241 * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or 242 * {@link Activity#RESULT_OK} if the message has been broadcast 243 * to applications 244 */ 245 protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) { 246 int index = 0; 247 248 int msgType = (0xFF & pdu[index++]); 249 if (msgType != 0) { 250 Rlog.w(TAG, "Received a WAP SMS which is not WDP. Discard."); 251 return Intents.RESULT_SMS_HANDLED; 252 } 253 int totalSegments = (0xFF & pdu[index++]); // >= 1 254 int segment = (0xFF & pdu[index++]); // >= 0 255 256 if (segment >= totalSegments) { 257 Rlog.e(TAG, "WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1)); 258 return Intents.RESULT_SMS_HANDLED; 259 } 260 261 // Only the first segment contains sourcePort and destination Port 262 int sourcePort = 0; 263 int destinationPort = 0; 264 if (segment == 0) { 265 //process WDP segment 266 sourcePort = (0xFF & pdu[index++]) << 8; 267 sourcePort |= 0xFF & pdu[index++]; 268 destinationPort = (0xFF & pdu[index++]) << 8; 269 destinationPort |= 0xFF & pdu[index++]; 270 // Some carriers incorrectly send duplicate port fields in omadm wap pushes. 271 // If configured, check for that here 272 if (mCheckForDuplicatePortsInOmadmWapPush) { 273 if (checkDuplicatePortOmadmWappush(pdu,index)) { 274 index = index + 4; // skip duplicate port fields 275 } 276 } 277 } 278 279 // Lookup all other related parts 280 Rlog.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address 281 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort 282 + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments); 283 284 // pass the user data portion of the PDU to the shared handler in SMSDispatcher 285 byte[] userData = new byte[pdu.length - index]; 286 System.arraycopy(pdu, index, userData, 0, pdu.length - index); 287 288 return processMessagePart(userData, address, referenceNumber, segment, totalSegments, 289 0L, destinationPort, true); 290 } 291 292 /** {@inheritDoc} */ 293 @Override 294 protected void sendData(String destAddr, String scAddr, int destPort, 295 byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { 296 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( 297 scAddr, destAddr, destPort, data, (deliveryIntent != null)); 298 HashMap map = SmsTrackerMapFactory(destAddr, scAddr, destPort, data, pdu); 299 SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent, 300 getFormat()); 301 sendSubmitPdu(tracker); 302 } 303 304 /** {@inheritDoc} */ 305 @Override 306 protected void sendText(String destAddr, String scAddr, String text, 307 PendingIntent sentIntent, PendingIntent deliveryIntent) { 308 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( 309 scAddr, destAddr, text, (deliveryIntent != null), null); 310 HashMap map = SmsTrackerMapFactory(destAddr, scAddr, text, pdu); 311 SmsTracker tracker = SmsTrackerFactory(map, sentIntent, 312 deliveryIntent, getFormat()); 313 sendSubmitPdu(tracker); 314 } 315 316 /** {@inheritDoc} */ 317 @Override 318 protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody, 319 boolean use7bitOnly) { 320 return SmsMessage.calculateLength(messageBody, use7bitOnly); 321 } 322 323 /** {@inheritDoc} */ 324 @Override 325 protected void sendNewSubmitPdu(String destinationAddress, String scAddress, 326 String message, SmsHeader smsHeader, int encoding, 327 PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) { 328 UserData uData = new UserData(); 329 uData.payloadStr = message; 330 uData.userDataHeader = smsHeader; 331 if (encoding == SmsConstants.ENCODING_7BIT) { 332 uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; 333 } else { // assume UTF-16 334 uData.msgEncoding = UserData.ENCODING_UNICODE_16; 335 } 336 uData.msgEncodingSet = true; 337 338 /* By setting the statusReportRequested bit only for the 339 * last message fragment, this will result in only one 340 * callback to the sender when that last fragment delivery 341 * has been acknowledged. */ 342 SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress, 343 uData, (deliveryIntent != null) && lastPart); 344 345 HashMap map = SmsTrackerMapFactory(destinationAddress, scAddress, 346 message, submitPdu); 347 SmsTracker tracker = SmsTrackerFactory(map, sentIntent, 348 deliveryIntent, getFormat()); 349 sendSubmitPdu(tracker); 350 } 351 352 protected void sendSubmitPdu(SmsTracker tracker) { 353 if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) { 354 if (tracker.mSentIntent != null) { 355 try { 356 tracker.mSentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE); 357 } catch (CanceledException ex) {} 358 } 359 if (VDBG) { 360 Rlog.d(TAG, "Block SMS in Emergency Callback mode"); 361 } 362 return; 363 } 364 sendRawPdu(tracker); 365 } 366 367 /** {@inheritDoc} */ 368 @Override 369 protected void sendSms(SmsTracker tracker) { 370 HashMap<String, Object> map = tracker.mData; 371 372 // byte smsc[] = (byte[]) map.get("smsc"); // unused for CDMA 373 byte pdu[] = (byte[]) map.get("pdu"); 374 375 Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker); 376 377 Rlog.d(TAG, "sendSms: " 378 +" isIms()="+isIms() 379 +" mRetryCount="+tracker.mRetryCount 380 +" mImsRetry="+tracker.mImsRetry 381 +" mMessageRef="+tracker.mMessageRef 382 +" SS=" +mPhone.getServiceState().getState()); 383 384 // sms over cdma is used: 385 // if sms over IMS is not supported AND 386 // this is not a retry case after sms over IMS failed 387 // indicated by mImsRetry > 0 388 if (0 == tracker.mImsRetry && !isIms()) { 389 mCi.sendCdmaSms(pdu, reply); 390 } else { 391 mCi.sendImsCdmaSms(pdu, tracker.mImsRetry, tracker.mMessageRef, reply); 392 // increment it here, so in case of SMS_FAIL_RETRY over IMS 393 // next retry will be sent using IMS request again. 394 tracker.mImsRetry++; 395 } 396 } 397 398 @Override 399 public void sendRetrySms(SmsTracker tracker) { 400 //re-routing to ImsSMSDispatcher 401 mImsSMSDispatcher.sendRetrySms(tracker); 402 } 403 404 /** {@inheritDoc} */ 405 @Override 406 protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) { 407 String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false"); 408 if (inEcm.equals("true")) { 409 return; 410 } 411 412 int causeCode = resultToCause(result); 413 mCi.acknowledgeLastIncomingCdmaSms(success, causeCode, response); 414 415 if (causeCode == 0) { 416 mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint; 417 } 418 mLastDispatchedSmsFingerprint = null; 419 } 420 421 private static int resultToCause(int rc) { 422 switch (rc) { 423 case Activity.RESULT_OK: 424 case Intents.RESULT_SMS_HANDLED: 425 // Cause code is ignored on success. 426 return 0; 427 case Intents.RESULT_SMS_OUT_OF_MEMORY: 428 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE; 429 case Intents.RESULT_SMS_UNSUPPORTED: 430 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID; 431 case Intents.RESULT_SMS_GENERIC_ERROR: 432 default: 433 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM; 434 } 435 } 436 437 /** 438 * Optional check to see if the received WapPush is an OMADM notification with erroneous 439 * extra port fields. 440 * - Some carriers make this mistake. 441 * ex: MSGTYPE-TotalSegments-CurrentSegment 442 * -SourcePortDestPort-SourcePortDestPort-OMADM PDU 443 * @param origPdu The WAP-WDP PDU segment 444 * @param index Current Index while parsing the PDU. 445 * @return True if OrigPdu is OmaDM Push Message which has duplicate ports. 446 * False if OrigPdu is NOT OmaDM Push Message which has duplicate ports. 447 */ 448 private static boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) { 449 index += 4; 450 byte[] omaPdu = new byte[origPdu.length - index]; 451 System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length); 452 453 WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu); 454 int wspIndex = 2; 455 456 // Process header length field 457 if (pduDecoder.decodeUintvarInteger(wspIndex) == false) { 458 return false; 459 } 460 461 wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field 462 463 // Process content type field 464 if (pduDecoder.decodeContentType(wspIndex) == false) { 465 return false; 466 } 467 468 String mimeType = pduDecoder.getValueString(); 469 if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI)) { 470 return true; 471 } 472 return false; 473 } 474 475 // Receiver for Service Category Program Data results. 476 // We already ACK'd the original SCPD SMS, so this sends a new response SMS. 477 // TODO: handle retries if the RIL returns an error. 478 private final BroadcastReceiver mScpResultsReceiver = new BroadcastReceiver() { 479 @Override 480 public void onReceive(Context context, Intent intent) { 481 int rc = getResultCode(); 482 boolean success = (rc == Activity.RESULT_OK) || (rc == Intents.RESULT_SMS_HANDLED); 483 if (!success) { 484 Rlog.e(TAG, "SCP results error: result code = " + rc); 485 return; 486 } 487 Bundle extras = getResultExtras(false); 488 if (extras == null) { 489 Rlog.e(TAG, "SCP results error: missing extras"); 490 return; 491 } 492 String sender = extras.getString("sender"); 493 if (sender == null) { 494 Rlog.e(TAG, "SCP results error: missing sender extra."); 495 return; 496 } 497 ArrayList<CdmaSmsCbProgramResults> results 498 = extras.getParcelableArrayList("results"); 499 if (results == null) { 500 Rlog.e(TAG, "SCP results error: missing results extra."); 501 return; 502 } 503 504 BearerData bData = new BearerData(); 505 bData.messageType = BearerData.MESSAGE_TYPE_SUBMIT; 506 bData.messageId = SmsMessage.getNextMessageId(); 507 bData.serviceCategoryProgramResults = results; 508 byte[] encodedBearerData = BearerData.encode(bData); 509 510 ByteArrayOutputStream baos = new ByteArrayOutputStream(100); 511 DataOutputStream dos = new DataOutputStream(baos); 512 try { 513 dos.writeInt(SmsEnvelope.TELESERVICE_SCPT); 514 dos.writeInt(0); //servicePresent 515 dos.writeInt(0); //serviceCategory 516 CdmaSmsAddress destAddr = CdmaSmsAddress.parse( 517 PhoneNumberUtils.cdmaCheckAndProcessPlusCode(sender)); 518 dos.write(destAddr.digitMode); 519 dos.write(destAddr.numberMode); 520 dos.write(destAddr.ton); // number_type 521 dos.write(destAddr.numberPlan); 522 dos.write(destAddr.numberOfDigits); 523 dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits 524 // Subaddress is not supported. 525 dos.write(0); //subaddressType 526 dos.write(0); //subaddr_odd 527 dos.write(0); //subaddr_nbr_of_digits 528 dos.write(encodedBearerData.length); 529 dos.write(encodedBearerData, 0, encodedBearerData.length); 530 // Ignore the RIL response. TODO: implement retry if SMS send fails. 531 mCi.sendCdmaSms(baos.toByteArray(), null); 532 } catch (IOException e) { 533 Rlog.e(TAG, "exception creating SCP results PDU", e); 534 } finally { 535 try { 536 dos.close(); 537 } catch (IOException ignored) { 538 } 539 } 540 } 541 }; 542 543 @Override 544 public boolean isIms() { 545 return mImsSMSDispatcher.isIms(); 546 } 547 548 @Override 549 public String getImsSmsFormat() { 550 return mImsSMSDispatcher.getImsSmsFormat(); 551 } 552} 553