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