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