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