GsmSMSDispatcher.java revision 43a17654cf4bfe7f1ec22bd8b7b32daccdf27c09
1/* 2 * Copyright (C) 2006 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.gsm; 18 19import android.app.Activity; 20import android.app.PendingIntent; 21import android.app.PendingIntent.CanceledException; 22import android.content.Intent; 23import android.os.AsyncResult; 24import android.os.Message; 25import android.os.SystemProperties; 26import android.provider.Telephony.Sms; 27import android.provider.Telephony.Sms.Intents; 28import android.telephony.ServiceState; 29import android.telephony.SmsCbMessage; 30import android.telephony.gsm.GsmCellLocation; 31import android.util.Log; 32 33import com.android.internal.telephony.BaseCommands; 34import com.android.internal.telephony.CommandsInterface; 35import com.android.internal.telephony.IccUtils; 36import com.android.internal.telephony.SMSDispatcher; 37import com.android.internal.telephony.SmsHeader; 38import com.android.internal.telephony.SmsMessageBase; 39import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; 40import com.android.internal.telephony.TelephonyProperties; 41 42import java.util.ArrayList; 43import java.util.HashMap; 44import java.util.Iterator; 45 46import static android.telephony.SmsMessage.MessageClass; 47 48final class GsmSMSDispatcher extends SMSDispatcher { 49 private static final String TAG = "GSM"; 50 51 private GSMPhone mGsmPhone; 52 53 GsmSMSDispatcher(GSMPhone phone) { 54 super(phone); 55 mGsmPhone = phone; 56 57 ((BaseCommands)mCm).setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null); 58 } 59 60 /** 61 * Called when a status report is received. This should correspond to 62 * a previously successful SEND. 63 * 64 * @param ar AsyncResult passed into the message handler. ar.result should 65 * be a String representing the status report PDU, as ASCII hex. 66 */ 67 @Override 68 protected void handleStatusReport(AsyncResult ar) { 69 String pduString = (String) ar.result; 70 SmsMessage sms = SmsMessage.newFromCDS(pduString); 71 72 int tpStatus = sms.getStatus(); 73 74 if (sms != null) { 75 int messageRef = sms.messageRef; 76 for (int i = 0, count = deliveryPendingList.size(); i < count; i++) { 77 SmsTracker tracker = deliveryPendingList.get(i); 78 if (tracker.mMessageRef == messageRef) { 79 // Found it. Remove from list and broadcast. 80 if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) { 81 deliveryPendingList.remove(i); 82 } 83 PendingIntent intent = tracker.mDeliveryIntent; 84 Intent fillIn = new Intent(); 85 fillIn.putExtra("pdu", IccUtils.hexStringToBytes(pduString)); 86 try { 87 intent.send(mContext, Activity.RESULT_OK, fillIn); 88 } catch (CanceledException ex) {} 89 90 // Only expect to see one tracker matching this messageref 91 break; 92 } 93 } 94 } 95 acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null); 96 } 97 98 99 /** {@inheritDoc} */ 100 @Override 101 public int dispatchMessage(SmsMessageBase smsb) { 102 103 // If sms is null, means there was a parsing error. 104 if (smsb == null) { 105 return Intents.RESULT_SMS_GENERIC_ERROR; 106 } 107 SmsMessage sms = (SmsMessage) smsb; 108 boolean handled = false; 109 110 if (sms.isTypeZero()) { 111 // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be 112 // Displayed/Stored/Notified. They should only be acknowledged. 113 Log.d(TAG, "Received short message type 0, Don't display or store it. Send Ack"); 114 return Intents.RESULT_SMS_HANDLED; 115 } 116 117 if (mSmsReceiveDisabled) { 118 // Device doesn't support SMS service, 119 Log.d(TAG, "Received short message on device which doesn't support " 120 + "SMS service. Ignored."); 121 return Intents.RESULT_SMS_HANDLED; 122 } 123 124 // Special case the message waiting indicator messages 125 if (sms.isMWISetMessage()) { 126 mGsmPhone.updateMessageWaitingIndicator(true); 127 handled = sms.isMwiDontStore(); 128 if (false) { 129 Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled); 130 } 131 } else if (sms.isMWIClearMessage()) { 132 mGsmPhone.updateMessageWaitingIndicator(false); 133 handled = sms.isMwiDontStore(); 134 if (false) { 135 Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled); 136 } 137 } 138 139 if (handled) { 140 return Intents.RESULT_SMS_HANDLED; 141 } 142 143 if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) { 144 // It's a storable message and there's no storage available. Bail. 145 // (See TS 23.038 for a description of class 0 messages.) 146 return Intents.RESULT_SMS_OUT_OF_MEMORY; 147 } 148 149 SmsHeader smsHeader = sms.getUserDataHeader(); 150 // See if message is partial or port addressed. 151 if ((smsHeader == null) || (smsHeader.concatRef == null)) { 152 // Message is not partial (not part of concatenated sequence). 153 byte[][] pdus = new byte[1][]; 154 pdus[0] = sms.getPdu(); 155 156 if (smsHeader != null && smsHeader.portAddrs != null) { 157 if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) { 158 return mWapPush.dispatchWapPdu(sms.getUserData()); 159 } else { 160 // The message was sent to a port, so concoct a URI for it. 161 dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort); 162 } 163 } else { 164 // Normal short and non-port-addressed message, dispatch it. 165 dispatchPdus(pdus); 166 } 167 return Activity.RESULT_OK; 168 } else { 169 // Process the message part. 170 return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs); 171 } 172 } 173 174 /** {@inheritDoc} */ 175 @Override 176 protected void sendData(String destAddr, String scAddr, int destPort, 177 byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { 178 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( 179 scAddr, destAddr, destPort, data, (deliveryIntent != null)); 180 sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent); 181 } 182 183 /** {@inheritDoc} */ 184 @Override 185 protected void sendText(String destAddr, String scAddr, String text, 186 PendingIntent sentIntent, PendingIntent deliveryIntent) { 187 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( 188 scAddr, destAddr, text, (deliveryIntent != null)); 189 sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent); 190 } 191 192 /** {@inheritDoc} */ 193 @Override 194 protected void sendMultipartText(String destinationAddress, String scAddress, 195 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, 196 ArrayList<PendingIntent> deliveryIntents) { 197 198 int refNumber = getNextConcatenatedRef() & 0x00FF; 199 int msgCount = parts.size(); 200 int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN; 201 202 mRemainingMessages = msgCount; 203 204 for (int i = 0; i < msgCount; i++) { 205 TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false); 206 if (encoding != details.codeUnitSize 207 && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN 208 || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) { 209 encoding = details.codeUnitSize; 210 } 211 } 212 213 for (int i = 0; i < msgCount; i++) { 214 SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); 215 concatRef.refNumber = refNumber; 216 concatRef.seqNumber = i + 1; // 1-based sequence 217 concatRef.msgCount = msgCount; 218 // TODO: We currently set this to true since our messaging app will never 219 // send more than 255 parts (it converts the message to MMS well before that). 220 // However, we should support 3rd party messaging apps that might need 16-bit 221 // references 222 // Note: It's not sufficient to just flip this bit to true; it will have 223 // ripple effects (several calculations assume 8-bit ref). 224 concatRef.isEightBits = true; 225 SmsHeader smsHeader = new SmsHeader(); 226 smsHeader.concatRef = concatRef; 227 228 PendingIntent sentIntent = null; 229 if (sentIntents != null && sentIntents.size() > i) { 230 sentIntent = sentIntents.get(i); 231 } 232 233 PendingIntent deliveryIntent = null; 234 if (deliveryIntents != null && deliveryIntents.size() > i) { 235 deliveryIntent = deliveryIntents.get(i); 236 } 237 238 SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress, 239 parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader), 240 encoding); 241 242 sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent); 243 } 244 } 245 246 /** 247 * Send a multi-part text based SMS which already passed SMS control check. 248 * 249 * It is the working function for sendMultipartText(). 250 * 251 * @param destinationAddress the address to send the message to 252 * @param scAddress is the service center address or null to use 253 * the current default SMSC 254 * @param parts an <code>ArrayList</code> of strings that, in order, 255 * comprise the original message 256 * @param sentIntents if not null, an <code>ArrayList</code> of 257 * <code>PendingIntent</code>s (one for each message part) that is 258 * broadcast when the corresponding message part has been sent. 259 * The result code will be <code>Activity.RESULT_OK<code> for success, 260 * or one of these errors: 261 * <code>RESULT_ERROR_GENERIC_FAILURE</code> 262 * <code>RESULT_ERROR_RADIO_OFF</code> 263 * <code>RESULT_ERROR_NULL_PDU</code>. 264 * @param deliveryIntents if not null, an <code>ArrayList</code> of 265 * <code>PendingIntent</code>s (one for each message part) that is 266 * broadcast when the corresponding message part has been delivered 267 * to the recipient. The raw pdu of the status report is in the 268 * extended data ("pdu"). 269 */ 270 private void sendMultipartTextWithPermit(String destinationAddress, 271 String scAddress, ArrayList<String> parts, 272 ArrayList<PendingIntent> sentIntents, 273 ArrayList<PendingIntent> deliveryIntents) { 274 275 // check if in service 276 int ss = mPhone.getServiceState().getState(); 277 if (ss != ServiceState.STATE_IN_SERVICE) { 278 for (int i = 0, count = parts.size(); i < count; i++) { 279 PendingIntent sentIntent = null; 280 if (sentIntents != null && sentIntents.size() > i) { 281 sentIntent = sentIntents.get(i); 282 } 283 SmsTracker tracker = SmsTrackerFactory(null, sentIntent, null); 284 handleNotInService(ss, tracker); 285 } 286 return; 287 } 288 289 int refNumber = getNextConcatenatedRef() & 0x00FF; 290 int msgCount = parts.size(); 291 int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN; 292 293 mRemainingMessages = msgCount; 294 295 for (int i = 0; i < msgCount; i++) { 296 TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false); 297 if (encoding != details.codeUnitSize 298 && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN 299 || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) { 300 encoding = details.codeUnitSize; 301 } 302 } 303 304 for (int i = 0; i < msgCount; i++) { 305 SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); 306 concatRef.refNumber = refNumber; 307 concatRef.seqNumber = i + 1; // 1-based sequence 308 concatRef.msgCount = msgCount; 309 concatRef.isEightBits = false; 310 SmsHeader smsHeader = new SmsHeader(); 311 smsHeader.concatRef = concatRef; 312 313 PendingIntent sentIntent = null; 314 if (sentIntents != null && sentIntents.size() > i) { 315 sentIntent = sentIntents.get(i); 316 } 317 318 PendingIntent deliveryIntent = null; 319 if (deliveryIntents != null && deliveryIntents.size() > i) { 320 deliveryIntent = deliveryIntents.get(i); 321 } 322 323 SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress, 324 parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader), 325 encoding); 326 327 HashMap<String, Object> map = new HashMap<String, Object>(); 328 map.put("smsc", pdus.encodedScAddress); 329 map.put("pdu", pdus.encodedMessage); 330 331 SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent); 332 sendSms(tracker); 333 } 334 } 335 336 /** {@inheritDoc} */ 337 @Override 338 protected void sendSms(SmsTracker tracker) { 339 HashMap<String, Object> map = tracker.mData; 340 341 byte smsc[] = (byte[]) map.get("smsc"); 342 byte pdu[] = (byte[]) map.get("pdu"); 343 344 Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker); 345 mCm.sendSMS(IccUtils.bytesToHexString(smsc), 346 IccUtils.bytesToHexString(pdu), reply); 347 } 348 349 /** 350 * Send the multi-part SMS based on multipart Sms tracker 351 * 352 * @param tracker holds the multipart Sms tracker ready to be sent 353 */ 354 @Override 355 protected void sendMultipartSms (SmsTracker tracker) { 356 ArrayList<String> parts; 357 ArrayList<PendingIntent> sentIntents; 358 ArrayList<PendingIntent> deliveryIntents; 359 360 HashMap<String, Object> map = tracker.mData; 361 362 String destinationAddress = (String) map.get("destination"); 363 String scAddress = (String) map.get("scaddress"); 364 365 parts = (ArrayList<String>) map.get("parts"); 366 sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents"); 367 deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents"); 368 369 sendMultipartTextWithPermit(destinationAddress, 370 scAddress, parts, sentIntents, deliveryIntents); 371 372 } 373 374 /** {@inheritDoc} */ 375 @Override 376 protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){ 377 // FIXME unit test leaves cm == null. this should change 378 if (mCm != null) { 379 mCm.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response); 380 } 381 } 382 383 /** {@inheritDoc} */ 384 @Override 385 public void activateCellBroadcastSms(int activate, Message response) { 386 // Unless CBS is implemented for GSM, this point should be unreachable. 387 Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM."); 388 response.recycle(); 389 } 390 391 /** {@inheritDoc} */ 392 @Override 393 public void getCellBroadcastSmsConfig(Message response){ 394 // Unless CBS is implemented for GSM, this point should be unreachable. 395 Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM."); 396 response.recycle(); 397 } 398 399 /** {@inheritDoc} */ 400 @Override 401 public void setCellBroadcastConfig(int[] configValuesArray, Message response) { 402 // Unless CBS is implemented for GSM, this point should be unreachable. 403 Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM."); 404 response.recycle(); 405 } 406 407 private int resultToCause(int rc) { 408 switch (rc) { 409 case Activity.RESULT_OK: 410 case Intents.RESULT_SMS_HANDLED: 411 // Cause code is ignored on success. 412 return 0; 413 case Intents.RESULT_SMS_OUT_OF_MEMORY: 414 return CommandsInterface.GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED; 415 case Intents.RESULT_SMS_GENERIC_ERROR: 416 default: 417 return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR; 418 } 419 } 420 421 /** 422 * Holds all info about a message page needed to assemble a complete 423 * concatenated message 424 */ 425 private static final class SmsCbConcatInfo { 426 private final SmsCbHeader mHeader; 427 428 private final String mPlmn; 429 430 private final int mLac; 431 432 private final int mCid; 433 434 public SmsCbConcatInfo(SmsCbHeader header, String plmn, int lac, int cid) { 435 mHeader = header; 436 mPlmn = plmn; 437 mLac = lac; 438 mCid = cid; 439 } 440 441 @Override 442 public int hashCode() { 443 return mHeader.messageIdentifier * 31 + mHeader.updateNumber; 444 } 445 446 @Override 447 public boolean equals(Object obj) { 448 if (obj instanceof SmsCbConcatInfo) { 449 SmsCbConcatInfo other = (SmsCbConcatInfo)obj; 450 451 // Two pages match if all header attributes (except the page 452 // index) are identical, and both pages belong to the same 453 // location (which is also determined by the scope parameter) 454 if (mHeader.geographicalScope == other.mHeader.geographicalScope 455 && mHeader.messageCode == other.mHeader.messageCode 456 && mHeader.updateNumber == other.mHeader.updateNumber 457 && mHeader.messageIdentifier == other.mHeader.messageIdentifier 458 && mHeader.dataCodingScheme == other.mHeader.dataCodingScheme 459 && mHeader.nrOfPages == other.mHeader.nrOfPages) { 460 return matchesLocation(other.mPlmn, other.mLac, other.mCid); 461 } 462 } 463 464 return false; 465 } 466 467 /** 468 * Checks if this concatenation info matches the given location. The 469 * granularity of the match depends on the geographical scope. 470 * 471 * @param plmn PLMN 472 * @param lac Location area code 473 * @param cid Cell ID 474 * @return true if matching, false otherwise 475 */ 476 public boolean matchesLocation(String plmn, int lac, int cid) { 477 switch (mHeader.geographicalScope) { 478 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: 479 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: 480 if (mCid != cid) { 481 return false; 482 } 483 // deliberate fall-through 484 case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: 485 if (mLac != lac) { 486 return false; 487 } 488 // deliberate fall-through 489 case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: 490 return mPlmn != null && mPlmn.equals(plmn); 491 } 492 493 return false; 494 } 495 } 496 497 // This map holds incomplete concatenated messages waiting for assembly 498 private HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap = 499 new HashMap<SmsCbConcatInfo, byte[][]>(); 500 501 protected void handleBroadcastSms(AsyncResult ar) { 502 try { 503 byte[][] pdus = null; 504 byte[] receivedPdu = (byte[])ar.result; 505 506 if (false) { 507 for (int i = 0; i < receivedPdu.length; i += 8) { 508 StringBuilder sb = new StringBuilder("SMS CB pdu data: "); 509 for (int j = i; j < i + 8 && j < receivedPdu.length; j++) { 510 int b = receivedPdu[j] & 0xff; 511 if (b < 0x10) { 512 sb.append("0"); 513 } 514 sb.append(Integer.toHexString(b)).append(" "); 515 } 516 Log.d(TAG, sb.toString()); 517 } 518 } 519 520 SmsCbHeader header = new SmsCbHeader(receivedPdu); 521 String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC); 522 GsmCellLocation cellLocation = (GsmCellLocation)mGsmPhone.getCellLocation(); 523 int lac = cellLocation.getLac(); 524 int cid = cellLocation.getCid(); 525 526 if (header.nrOfPages > 1) { 527 // Multi-page message 528 SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, plmn, lac, cid); 529 530 // Try to find other pages of the same message 531 pdus = mSmsCbPageMap.get(concatInfo); 532 533 if (pdus == null) { 534 // This it the first page of this message, make room for all 535 // pages and keep until complete 536 pdus = new byte[header.nrOfPages][]; 537 538 mSmsCbPageMap.put(concatInfo, pdus); 539 } 540 541 // Page parameter is one-based 542 pdus[header.pageIndex - 1] = receivedPdu; 543 544 for (int i = 0; i < pdus.length; i++) { 545 if (pdus[i] == null) { 546 // Still missing pages, exit 547 return; 548 } 549 } 550 551 // Message complete, remove and dispatch 552 mSmsCbPageMap.remove(concatInfo); 553 } else { 554 // Single page message 555 pdus = new byte[1][]; 556 pdus[0] = receivedPdu; 557 } 558 559 dispatchBroadcastPdus(pdus); 560 561 // Remove messages that are out of scope to prevent the map from 562 // growing indefinitely, containing incomplete messages that were 563 // never assembled 564 Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator(); 565 566 while (iter.hasNext()) { 567 SmsCbConcatInfo info = iter.next(); 568 569 if (!info.matchesLocation(plmn, lac, cid)) { 570 iter.remove(); 571 } 572 } 573 } catch (RuntimeException e) { 574 Log.e(TAG, "Error in decoding SMS CB pdu", e); 575 } 576 } 577 578} 579