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