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