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