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