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