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