CdmaSMSDispatcher.java revision 059fe88115ccb70d9870698e490ed5b4c88da50c
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.AsyncResult; 29import android.os.Message; 30import android.os.SystemProperties; 31import android.preference.PreferenceManager; 32import android.provider.Telephony; 33import android.provider.Telephony.Sms.Intents; 34import android.telephony.SmsManager; 35import android.telephony.SmsMessage.MessageClass; 36import android.util.Config; 37import android.util.Log; 38 39import com.android.internal.telephony.CommandsInterface; 40import com.android.internal.telephony.SMSDispatcher; 41import com.android.internal.telephony.SmsHeader; 42import com.android.internal.telephony.SmsMessageBase; 43import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; 44import com.android.internal.telephony.TelephonyProperties; 45import com.android.internal.telephony.cdma.sms.SmsEnvelope; 46import com.android.internal.telephony.cdma.sms.UserData; 47import com.android.internal.util.HexDump; 48 49import java.io.ByteArrayOutputStream; 50import java.util.ArrayList; 51import java.util.Arrays; 52import java.util.HashMap; 53 54 55final class CdmaSMSDispatcher extends SMSDispatcher { 56 private static final String TAG = "CDMA"; 57 58 private byte[] mLastDispatchedSmsFingerprint; 59 private byte[] mLastAcknowledgedSmsFingerprint; 60 61 CdmaSMSDispatcher(CDMAPhone phone) { 62 super(phone); 63 } 64 65 /** 66 * Called when a status report is received. This should correspond to 67 * a previously successful SEND. 68 * Is a special GSM function, should never be called in CDMA!! 69 * 70 * @param ar AsyncResult passed into the message handler. ar.result should 71 * be a String representing the status report PDU, as ASCII hex. 72 */ 73 protected void handleStatusReport(AsyncResult ar) { 74 Log.d(TAG, "handleStatusReport is a special GSM function, should never be called in CDMA!"); 75 } 76 77 private void handleCdmaStatusReport(SmsMessage sms) { 78 for (int i = 0, count = deliveryPendingList.size(); i < count; i++) { 79 SmsTracker tracker = deliveryPendingList.get(i); 80 if (tracker.mMessageRef == sms.messageRef) { 81 // Found it. Remove from list and broadcast. 82 deliveryPendingList.remove(i); 83 PendingIntent intent = tracker.mDeliveryIntent; 84 Intent fillIn = new Intent(); 85 fillIn.putExtra("pdu", sms.getPdu()); 86 try { 87 intent.send(mContext, Activity.RESULT_OK, fillIn); 88 } catch (CanceledException ex) {} 89 break; // Only expect to see one tracker matching this message. 90 } 91 } 92 } 93 94 /** {@inheritDoc} */ 95 protected int dispatchMessage(SmsMessageBase smsb) { 96 97 // If sms is null, means there was a parsing error. 98 if (smsb == null) { 99 Log.e(TAG, "dispatchMessage: message is null"); 100 return Intents.RESULT_SMS_GENERIC_ERROR; 101 } 102 103 String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false"); 104 if (inEcm.equals("true")) { 105 return Activity.RESULT_OK; 106 } 107 108 // See if we have a network duplicate SMS. 109 SmsMessage sms = (SmsMessage) smsb; 110 mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint(); 111 if (mLastAcknowledgedSmsFingerprint != null && 112 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) { 113 return Intents.RESULT_SMS_HANDLED; 114 } 115 // Decode BD stream and set sms variables. 116 sms.parseSms(); 117 int teleService = sms.getTeleService(); 118 boolean handled = false; 119 120 if ((SmsEnvelope.TELESERVICE_VMN == teleService) || 121 (SmsEnvelope.TELESERVICE_MWI == teleService)) { 122 // handling Voicemail 123 int voicemailCount = sms.getNumOfVoicemails(); 124 Log.d(TAG, "Voicemail count=" + voicemailCount); 125 // Store the voicemail count in preferences. 126 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( 127 mPhone.getContext()); 128 SharedPreferences.Editor editor = sp.edit(); 129 editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount); 130 editor.commit(); 131 ((CDMAPhone) mPhone).updateMessageWaitingIndicator(voicemailCount); 132 handled = true; 133 } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) || 134 (SmsEnvelope.TELESERVICE_WEMT == teleService)) && 135 sms.isStatusReportMessage()) { 136 handleCdmaStatusReport(sms); 137 handled = true; 138 } else if ((sms.getUserData() == null)) { 139 if (Config.LOGD) { 140 Log.d(TAG, "Received SMS without user data"); 141 } 142 handled = true; 143 } 144 145 if (handled) { 146 return Intents.RESULT_SMS_HANDLED; 147 } 148 149 if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) { 150 // It's a storable message and there's no storage available. Bail. 151 // (See C.S0015-B v2.0 for a description of "Immediate Display" 152 // messages, which we represent as CLASS_0.) 153 return Intents.RESULT_SMS_OUT_OF_MEMORY; 154 } 155 156 if (SmsEnvelope.TELESERVICE_WAP == teleService) { 157 return processCdmaWapPdu(sms.getUserData(), sms.messageRef, 158 sms.getOriginatingAddress()); 159 } 160 161 // Reject (NAK) any messages with teleservice ids that have 162 // not yet been handled and also do not correspond to the two 163 // kinds that are processed below. 164 if ((SmsEnvelope.TELESERVICE_WMT != teleService) && 165 (SmsEnvelope.TELESERVICE_WEMT != teleService) && 166 (SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) { 167 return Intents.RESULT_SMS_UNSUPPORTED; 168 } 169 170 /* 171 * TODO(cleanup): Why are we using a getter method for this 172 * (and for so many other sms fields)? Trivial getters and 173 * setters like this are direct violations of the style guide. 174 * If the purpose is to protect against writes (by not 175 * providing a setter) then any protection is illusory (and 176 * hence bad) for cases where the values are not primitives, 177 * such as this call for the header. Since this is an issue 178 * with the public API it cannot be changed easily, but maybe 179 * something can be done eventually. 180 */ 181 SmsHeader smsHeader = sms.getUserDataHeader(); 182 183 /* 184 * TODO(cleanup): Since both CDMA and GSM use the same header 185 * format, this dispatch processing is naturally identical, 186 * and code should probably not be replicated explicitly. 187 */ 188 189 // See if message is partial or port addressed. 190 if ((smsHeader == null) || (smsHeader.concatRef == null)) { 191 // Message is not partial (not part of concatenated sequence). 192 byte[][] pdus = new byte[1][]; 193 pdus[0] = sms.getPdu(); 194 195 if (smsHeader != null && smsHeader.portAddrs != null) { 196 if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) { 197 // GSM-style WAP indication 198 return mWapPush.dispatchWapPdu(sms.getUserData()); 199 } else { 200 // The message was sent to a port, so concoct a URI for it. 201 dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort); 202 } 203 } else { 204 // Normal short and non-port-addressed message, dispatch it. 205 dispatchPdus(pdus); 206 } 207 return Activity.RESULT_OK; 208 } else { 209 // Process the message part. 210 return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs); 211 } 212 } 213 214 /** 215 * Processes inbound messages that are in the WAP-WDP PDU format. See 216 * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format. 217 * WDP segments are gathered until a datagram completes and gets dispatched. 218 * 219 * @param pdu The WAP-WDP PDU segment 220 * @return a result code from {@link Telephony.Sms.Intents}, or 221 * {@link Activity#RESULT_OK} if the message has been broadcast 222 * to applications 223 */ 224 protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) { 225 int segment; 226 int totalSegments; 227 int index = 0; 228 int msgType; 229 230 int sourcePort = 0; 231 int destinationPort = 0; 232 233 msgType = pdu[index++]; 234 if (msgType != 0){ 235 Log.w(TAG, "Received a WAP SMS which is not WDP. Discard."); 236 return Intents.RESULT_SMS_HANDLED; 237 } 238 totalSegments = pdu[index++]; // >=1 239 segment = pdu[index++]; // >=0 240 241 // Only the first segment contains sourcePort and destination Port 242 if (segment == 0) { 243 //process WDP segment 244 sourcePort = (0xFF & pdu[index++]) << 8; 245 sourcePort |= 0xFF & pdu[index++]; 246 destinationPort = (0xFF & pdu[index++]) << 8; 247 destinationPort |= 0xFF & pdu[index++]; 248 } 249 250 // Lookup all other related parts 251 StringBuilder where = new StringBuilder("reference_number ="); 252 where.append(referenceNumber); 253 where.append(" AND address = ?"); 254 String[] whereArgs = new String[] {address}; 255 256 Log.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address 257 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort 258 + ", ID = " + referenceNumber + ", segment# = " + segment + "/" + totalSegments); 259 260 byte[][] pdus = null; 261 Cursor cursor = null; 262 try { 263 cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null); 264 int cursorCount = cursor.getCount(); 265 if (cursorCount != totalSegments - 1) { 266 // We don't have all the parts yet, store this one away 267 ContentValues values = new ContentValues(); 268 values.put("date", new Long(0)); 269 values.put("pdu", HexDump.toHexString(pdu, index, pdu.length - index)); 270 values.put("address", address); 271 values.put("reference_number", referenceNumber); 272 values.put("count", totalSegments); 273 values.put("sequence", segment); 274 values.put("destination_port", destinationPort); 275 276 mResolver.insert(mRawUri, values); 277 278 return Intents.RESULT_SMS_HANDLED; 279 } 280 281 // All the parts are in place, deal with them 282 int pduColumn = cursor.getColumnIndex("pdu"); 283 int sequenceColumn = cursor.getColumnIndex("sequence"); 284 285 pdus = new byte[totalSegments][]; 286 for (int i = 0; i < cursorCount; i++) { 287 cursor.moveToNext(); 288 int cursorSequence = (int)cursor.getLong(sequenceColumn); 289 // Read the destination port from the first segment 290 if (cursorSequence == 0) { 291 int destinationPortColumn = cursor.getColumnIndex("destination_port"); 292 destinationPort = (int)cursor.getLong(destinationPortColumn); 293 } 294 pdus[cursorSequence] = HexDump.hexStringToByteArray( 295 cursor.getString(pduColumn)); 296 } 297 // The last part will be added later 298 299 // Remove the parts from the database 300 mResolver.delete(mRawUri, where.toString(), whereArgs); 301 } catch (SQLException e) { 302 Log.e(TAG, "Can't access multipart SMS database", e); 303 return Intents.RESULT_SMS_GENERIC_ERROR; 304 } finally { 305 if (cursor != null) cursor.close(); 306 } 307 308 // Build up the data stream 309 ByteArrayOutputStream output = new ByteArrayOutputStream(); 310 for (int i = 0; i < totalSegments; i++) { 311 // reassemble the (WSP-)pdu 312 if (i == segment) { 313 // This one isn't in the DB, so add it 314 output.write(pdu, index, pdu.length - index); 315 } else { 316 output.write(pdus[i], 0, pdus[i].length); 317 } 318 } 319 320 byte[] datagram = output.toByteArray(); 321 // Dispatch the PDU to applications 322 switch (destinationPort) { 323 case SmsHeader.PORT_WAP_PUSH: 324 // Handle the PUSH 325 return mWapPush.dispatchWapPdu(datagram); 326 327 default:{ 328 pdus = new byte[1][]; 329 pdus[0] = datagram; 330 // The messages were sent to any other WAP port 331 dispatchPortAddressedPdus(pdus, destinationPort); 332 return Activity.RESULT_OK; 333 } 334 } 335 } 336 337 /** {@inheritDoc} */ 338 protected void sendData(String destAddr, String scAddr, int destPort, 339 byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { 340 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( 341 scAddr, destAddr, destPort, data, (deliveryIntent != null)); 342 sendSubmitPdu(pdu, sentIntent, deliveryIntent); 343 } 344 345 /** {@inheritDoc} */ 346 protected void sendText(String destAddr, String scAddr, String text, 347 PendingIntent sentIntent, PendingIntent deliveryIntent) { 348 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( 349 scAddr, destAddr, text, (deliveryIntent != null), null); 350 sendSubmitPdu(pdu, sentIntent, deliveryIntent); 351 } 352 353 /** {@inheritDoc} */ 354 protected void sendMultipartText(String destAddr, String scAddr, 355 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, 356 ArrayList<PendingIntent> deliveryIntents) { 357 358 /** 359 * TODO(cleanup): There is no real code difference between 360 * this and the GSM version, and hence it should be moved to 361 * the base class or consolidated somehow, provided calling 362 * the proper submitpdu stuff can be arranged. 363 */ 364 365 int refNumber = getNextConcatenatedRef() & 0x00FF; 366 int msgCount = parts.size(); 367 int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN; 368 369 for (int i = 0; i < msgCount; i++) { 370 TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false); 371 if (encoding != details.codeUnitSize 372 && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN 373 || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) { 374 encoding = details.codeUnitSize; 375 } 376 } 377 378 for (int i = 0; i < msgCount; i++) { 379 SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); 380 concatRef.refNumber = refNumber; 381 concatRef.seqNumber = i + 1; // 1-based sequence 382 concatRef.msgCount = msgCount; 383 concatRef.isEightBits = true; 384 SmsHeader smsHeader = new SmsHeader(); 385 smsHeader.concatRef = concatRef; 386 387 PendingIntent sentIntent = null; 388 if (sentIntents != null && sentIntents.size() > i) { 389 sentIntent = sentIntents.get(i); 390 } 391 392 PendingIntent deliveryIntent = null; 393 if (deliveryIntents != null && deliveryIntents.size() > i) { 394 deliveryIntent = deliveryIntents.get(i); 395 } 396 397 UserData uData = new UserData(); 398 uData.payloadStr = parts.get(i); 399 uData.userDataHeader = smsHeader; 400 if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) { 401 uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; 402 } else { // assume UTF-16 403 uData.msgEncoding = UserData.ENCODING_UNICODE_16; 404 } 405 uData.msgEncodingSet = true; 406 407 /* By setting the statusReportRequested bit only for the 408 * last message fragment, this will result in only one 409 * callback to the sender when that last fragment delivery 410 * has been acknowledged. */ 411 SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destAddr, 412 uData, (deliveryIntent != null) && (i == (msgCount - 1))); 413 414 sendSubmitPdu(submitPdu, sentIntent, deliveryIntent); 415 } 416 } 417 418 protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu, 419 PendingIntent sentIntent, PendingIntent deliveryIntent) { 420 if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) { 421 if (sentIntent != null) { 422 try { 423 sentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE); 424 } catch (CanceledException ex) {} 425 } 426 if (Config.LOGD) { 427 Log.d(TAG, "Block SMS in Emergency Callback mode"); 428 } 429 return; 430 } 431 sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent); 432 } 433 434 /** {@inheritDoc} */ 435 protected void sendSms(SmsTracker tracker) { 436 HashMap map = tracker.mData; 437 438 // byte smsc[] = (byte[]) map.get("smsc"); // unused for CDMA 439 byte pdu[] = (byte[]) map.get("pdu"); 440 441 Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker); 442 443 mCm.sendCdmaSms(pdu, reply); 444 } 445 446 /** {@inheritDoc} */ 447 protected void sendMultipartSms (SmsTracker tracker) { 448 Log.d(TAG, "TODO: CdmaSMSDispatcher.sendMultipartSms not implemented"); 449 } 450 451 /** {@inheritDoc} */ 452 protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){ 453 // FIXME unit test leaves cm == null. this should change 454 455 String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false"); 456 if (inEcm.equals("true")) { 457 return; 458 } 459 460 if (mCm != null) { 461 int causeCode = resultToCause(result); 462 mCm.acknowledgeLastIncomingCdmaSms(success, causeCode, response); 463 464 if (causeCode == 0) { 465 mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint; 466 } 467 mLastDispatchedSmsFingerprint = null; 468 } 469 } 470 471 /** {@inheritDoc} */ 472 protected void activateCellBroadcastSms(int activate, Message response) { 473 mCm.setCdmaBroadcastActivation((activate == 0), response); 474 } 475 476 /** {@inheritDoc} */ 477 protected void getCellBroadcastSmsConfig(Message response) { 478 mCm.getCdmaBroadcastConfig(response); 479 } 480 481 /** {@inheritDoc} */ 482 protected void setCellBroadcastConfig(int[] configValuesArray, Message response) { 483 mCm.setCdmaBroadcastConfig(configValuesArray, response); 484 } 485 486 private int resultToCause(int rc) { 487 switch (rc) { 488 case Activity.RESULT_OK: 489 case Intents.RESULT_SMS_HANDLED: 490 // Cause code is ignored on success. 491 return 0; 492 case Intents.RESULT_SMS_OUT_OF_MEMORY: 493 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE; 494 case Intents.RESULT_SMS_UNSUPPORTED: 495 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID; 496 case Intents.RESULT_SMS_GENERIC_ERROR: 497 default: 498 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM; 499 } 500 } 501} 502