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