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