GsmSMSDispatcher.java revision 454c5783e7695feec276325920550b2d4dcc3e16
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.PhoneNumberUtils; 29import android.telephony.SmsCbLocation; 30import android.telephony.SmsCbMessage; 31import android.telephony.SmsManager; 32import android.telephony.gsm.GsmCellLocation; 33import android.telephony.Rlog; 34 35import com.android.internal.telephony.CommandsInterface; 36import com.android.internal.telephony.GsmAlphabet; 37import com.android.internal.telephony.PhoneBase; 38import com.android.internal.telephony.SmsConstants; 39import com.android.internal.telephony.SMSDispatcher; 40import com.android.internal.telephony.SmsHeader; 41import com.android.internal.telephony.SmsMessageBase; 42import com.android.internal.telephony.SmsStorageMonitor; 43import com.android.internal.telephony.SmsUsageMonitor; 44import com.android.internal.telephony.TelephonyProperties; 45import com.android.internal.telephony.uicc.IccUtils; 46import com.android.internal.telephony.uicc.UsimServiceTable; 47 48import java.util.HashMap; 49import java.util.Iterator; 50 51public final class GsmSMSDispatcher extends SMSDispatcher { 52 private static final String TAG = "GSM"; 53 54 /** Status report received */ 55 private static final int EVENT_NEW_SMS_STATUS_REPORT = 100; 56 57 /** New broadcast SMS */ 58 private static final int EVENT_NEW_BROADCAST_SMS = 101; 59 60 /** Result of writing SM to UICC (when SMS-PP service is not available). */ 61 private static final int EVENT_WRITE_SMS_COMPLETE = 102; 62 63 /** Handler for SMS-PP data download messages to UICC. */ 64 private final UsimDataDownloadHandler mDataDownloadHandler; 65 66 public GsmSMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor, 67 SmsUsageMonitor usageMonitor) { 68 super(phone, storageMonitor, usageMonitor); 69 mDataDownloadHandler = new UsimDataDownloadHandler(mCm); 70 mCm.setOnNewGsmSms(this, EVENT_NEW_SMS, null); 71 mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null); 72 mCm.setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null); 73 } 74 75 @Override 76 public void dispose() { 77 mCm.unSetOnNewGsmSms(this); 78 mCm.unSetOnSmsStatus(this); 79 mCm.unSetOnNewGsmBroadcastSms(this); 80 } 81 82 @Override 83 protected String getFormat() { 84 return SmsConstants.FORMAT_3GPP; 85 } 86 87 /** 88 * Handles 3GPP format-specific events coming from the phone stack. 89 * Other events are handled by {@link SMSDispatcher#handleMessage}. 90 * 91 * @param msg the message to handle 92 */ 93 @Override 94 public void handleMessage(Message msg) { 95 switch (msg.what) { 96 case EVENT_NEW_SMS_STATUS_REPORT: 97 handleStatusReport((AsyncResult) msg.obj); 98 break; 99 100 case EVENT_NEW_BROADCAST_SMS: 101 handleBroadcastSms((AsyncResult)msg.obj); 102 break; 103 104 case EVENT_WRITE_SMS_COMPLETE: 105 AsyncResult ar = (AsyncResult) msg.obj; 106 if (ar.exception == null) { 107 Rlog.d(TAG, "Successfully wrote SMS-PP message to UICC"); 108 mCm.acknowledgeLastIncomingGsmSms(true, 0, null); 109 } else { 110 Rlog.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception); 111 mCm.acknowledgeLastIncomingGsmSms(false, 112 CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null); 113 } 114 break; 115 116 default: 117 super.handleMessage(msg); 118 } 119 } 120 121 /** 122 * Called when a status report is received. This should correspond to 123 * a previously successful SEND. 124 * 125 * @param ar AsyncResult passed into the message handler. ar.result should 126 * be a String representing the status report PDU, as ASCII hex. 127 */ 128 private void handleStatusReport(AsyncResult ar) { 129 String pduString = (String) ar.result; 130 SmsMessage sms = SmsMessage.newFromCDS(pduString); 131 132 if (sms != null) { 133 int tpStatus = sms.getStatus(); 134 int messageRef = sms.messageRef; 135 for (int i = 0, count = deliveryPendingList.size(); i < count; i++) { 136 SmsTracker tracker = deliveryPendingList.get(i); 137 if (tracker.mMessageRef == messageRef) { 138 // Found it. Remove from list and broadcast. 139 if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) { 140 deliveryPendingList.remove(i); 141 } 142 PendingIntent intent = tracker.mDeliveryIntent; 143 Intent fillIn = new Intent(); 144 fillIn.putExtra("pdu", IccUtils.hexStringToBytes(pduString)); 145 fillIn.putExtra("format", SmsConstants.FORMAT_3GPP); 146 try { 147 intent.send(mContext, Activity.RESULT_OK, fillIn); 148 } catch (CanceledException ex) {} 149 150 // Only expect to see one tracker matching this messageref 151 break; 152 } 153 } 154 } 155 acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null); 156 } 157 158 /** {@inheritDoc} */ 159 @Override 160 public int dispatchMessage(SmsMessageBase smsb) { 161 162 // If sms is null, means there was a parsing error. 163 if (smsb == null) { 164 Rlog.e(TAG, "dispatchMessage: message is null"); 165 return Intents.RESULT_SMS_GENERIC_ERROR; 166 } 167 168 SmsMessage sms = (SmsMessage) smsb; 169 170 if (sms.isTypeZero()) { 171 // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be 172 // Displayed/Stored/Notified. They should only be acknowledged. 173 Rlog.d(TAG, "Received short message type 0, Don't display or store it. Send Ack"); 174 return Intents.RESULT_SMS_HANDLED; 175 } 176 177 // Send SMS-PP data download messages to UICC. See 3GPP TS 31.111 section 7.1.1. 178 if (sms.isUsimDataDownload()) { 179 UsimServiceTable ust = mPhone.getUsimServiceTable(); 180 // If we receive an SMS-PP message before the UsimServiceTable has been loaded, 181 // assume that the data download service is not present. This is very unlikely to 182 // happen because the IMS connection will not be established until after the ISIM 183 // records have been loaded, after the USIM service table has been loaded. 184 if (ust != null && ust.isAvailable( 185 UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) { 186 Rlog.d(TAG, "Received SMS-PP data download, sending to UICC."); 187 return mDataDownloadHandler.startDataDownload(sms); 188 } else { 189 Rlog.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC."); 190 String smsc = IccUtils.bytesToHexString( 191 PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength( 192 sms.getServiceCenterAddress())); 193 mCm.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc, 194 IccUtils.bytesToHexString(sms.getPdu()), 195 obtainMessage(EVENT_WRITE_SMS_COMPLETE)); 196 return Activity.RESULT_OK; // acknowledge after response from write to USIM 197 } 198 } 199 200 if (mSmsReceiveDisabled) { 201 // Device doesn't support SMS service, 202 Rlog.d(TAG, "Received short message on device which doesn't support " 203 + "SMS service. Ignored."); 204 return Intents.RESULT_SMS_HANDLED; 205 } 206 207 // Special case the message waiting indicator messages 208 boolean handled = false; 209 if (sms.isMWISetMessage()) { 210 mPhone.setVoiceMessageWaiting(1, -1); // line 1: unknown number of msgs waiting 211 handled = sms.isMwiDontStore(); 212 if (false) { 213 Rlog.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled); 214 } 215 } else if (sms.isMWIClearMessage()) { 216 mPhone.setVoiceMessageWaiting(1, 0); // line 1: no msgs waiting 217 handled = sms.isMwiDontStore(); 218 if (false) { 219 Rlog.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled); 220 } 221 } 222 223 if (handled) { 224 return Intents.RESULT_SMS_HANDLED; 225 } 226 227 if (!mStorageMonitor.isStorageAvailable() && 228 sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) { 229 // It's a storable message and there's no storage available. Bail. 230 // (See TS 23.038 for a description of class 0 messages.) 231 return Intents.RESULT_SMS_OUT_OF_MEMORY; 232 } 233 234 return dispatchNormalMessage(smsb); 235 } 236 237 /** {@inheritDoc} */ 238 @Override 239 protected void sendData(String destAddr, String scAddr, int destPort, 240 byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { 241 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( 242 scAddr, destAddr, destPort, data, (deliveryIntent != null)); 243 if (pdu != null) { 244 sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, 245 destAddr); 246 } else { 247 Rlog.e(TAG, "GsmSMSDispatcher.sendData(): getSubmitPdu() returned null"); 248 } 249 } 250 251 /** {@inheritDoc} */ 252 @Override 253 protected void sendText(String destAddr, String scAddr, String text, 254 PendingIntent sentIntent, PendingIntent deliveryIntent) { 255 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( 256 scAddr, destAddr, text, (deliveryIntent != null)); 257 if (pdu != null) { 258 sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, 259 destAddr); 260 } else { 261 Rlog.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null"); 262 } 263 } 264 265 /** {@inheritDoc} */ 266 @Override 267 protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody, 268 boolean use7bitOnly) { 269 return SmsMessage.calculateLength(messageBody, use7bitOnly); 270 } 271 272 /** {@inheritDoc} */ 273 @Override 274 protected void sendNewSubmitPdu(String destinationAddress, String scAddress, 275 String message, SmsHeader smsHeader, int encoding, 276 PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) { 277 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress, 278 message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader), 279 encoding, smsHeader.languageTable, smsHeader.languageShiftTable); 280 if (pdu != null) { 281 sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, 282 destinationAddress); 283 } else { 284 Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null"); 285 } 286 } 287 288 /** {@inheritDoc} */ 289 @Override 290 protected void sendSms(SmsTracker tracker) { 291 HashMap<String, Object> map = tracker.mData; 292 293 byte smsc[] = (byte[]) map.get("smsc"); 294 byte pdu[] = (byte[]) map.get("pdu"); 295 296 Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker); 297 298 if (tracker.mRetryCount > 0) { 299 Rlog.d(TAG, "sendSms: " 300 + " mRetryCount=" + tracker.mRetryCount 301 + " mMessageRef=" + tracker.mMessageRef 302 + " SS=" + mPhone.getServiceState().getState()); 303 304 // per TS 23.040 Section 9.2.3.6: If TP-MTI SMS-SUBMIT (0x01) type 305 // TP-RD (bit 2) is 1 for retry 306 // and TP-MR is set to previously failed sms TP-MR 307 if (((0x01 & pdu[0]) == 0x01)) { 308 pdu[0] |= 0x04; // TP-RD 309 pdu[1] = (byte) tracker.mMessageRef; // TP-MR 310 } 311 } 312 mCm.sendSMS(IccUtils.bytesToHexString(smsc), IccUtils.bytesToHexString(pdu), reply); 313 } 314 315 /** {@inheritDoc} */ 316 @Override 317 protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) { 318 mCm.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response); 319 } 320 321 private static int resultToCause(int rc) { 322 switch (rc) { 323 case Activity.RESULT_OK: 324 case Intents.RESULT_SMS_HANDLED: 325 // Cause code is ignored on success. 326 return 0; 327 case Intents.RESULT_SMS_OUT_OF_MEMORY: 328 return CommandsInterface.GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED; 329 case Intents.RESULT_SMS_GENERIC_ERROR: 330 default: 331 return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR; 332 } 333 } 334 335 /** 336 * Holds all info about a message page needed to assemble a complete 337 * concatenated message 338 */ 339 private static final class SmsCbConcatInfo { 340 341 private final SmsCbHeader mHeader; 342 private final SmsCbLocation mLocation; 343 344 public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) { 345 mHeader = header; 346 mLocation = location; 347 } 348 349 @Override 350 public int hashCode() { 351 return (mHeader.getSerialNumber() * 31) + mLocation.hashCode(); 352 } 353 354 @Override 355 public boolean equals(Object obj) { 356 if (obj instanceof SmsCbConcatInfo) { 357 SmsCbConcatInfo other = (SmsCbConcatInfo)obj; 358 359 // Two pages match if they have the same serial number (which includes the 360 // geographical scope and update number), and both pages belong to the same 361 // location (PLMN, plus LAC and CID if these are part of the geographical scope). 362 return mHeader.getSerialNumber() == other.mHeader.getSerialNumber() 363 && mLocation.equals(other.mLocation); 364 } 365 366 return false; 367 } 368 369 /** 370 * Compare the location code for this message to the current location code. The match is 371 * relative to the geographical scope of the message, which determines whether the LAC 372 * and Cell ID are saved in mLocation or set to -1 to match all values. 373 * 374 * @param plmn the current PLMN 375 * @param lac the current Location Area (GSM) or Service Area (UMTS) 376 * @param cid the current Cell ID 377 * @return true if this message is valid for the current location; false otherwise 378 */ 379 public boolean matchesLocation(String plmn, int lac, int cid) { 380 return mLocation.isInLocationArea(plmn, lac, cid); 381 } 382 } 383 384 // This map holds incomplete concatenated messages waiting for assembly 385 private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap = 386 new HashMap<SmsCbConcatInfo, byte[][]>(); 387 388 /** 389 * Handle 3GPP format SMS-CB message. 390 * @param ar the AsyncResult containing the received PDUs 391 */ 392 private void handleBroadcastSms(AsyncResult ar) { 393 try { 394 byte[] receivedPdu = (byte[])ar.result; 395 396 if (false) { 397 for (int i = 0; i < receivedPdu.length; i += 8) { 398 StringBuilder sb = new StringBuilder("SMS CB pdu data: "); 399 for (int j = i; j < i + 8 && j < receivedPdu.length; j++) { 400 int b = receivedPdu[j] & 0xff; 401 if (b < 0x10) { 402 sb.append('0'); 403 } 404 sb.append(Integer.toHexString(b)).append(' '); 405 } 406 Rlog.d(TAG, sb.toString()); 407 } 408 } 409 410 SmsCbHeader header = new SmsCbHeader(receivedPdu); 411 String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC); 412 int lac = -1; 413 int cid = -1; 414 android.telephony.CellLocation cl = mPhone.getCellLocation(); 415 // Check if cell location is GsmCellLocation. This is required to support 416 // dual-mode devices such as CDMA/LTE devices that require support for 417 // both 3GPP and 3GPP2 format messages 418 if (cl instanceof GsmCellLocation) { 419 GsmCellLocation cellLocation = (GsmCellLocation)cl; 420 lac = cellLocation.getLac(); 421 cid = cellLocation.getCid(); 422 } 423 424 SmsCbLocation location; 425 switch (header.getGeographicalScope()) { 426 case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: 427 location = new SmsCbLocation(plmn, lac, -1); 428 break; 429 430 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: 431 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: 432 location = new SmsCbLocation(plmn, lac, cid); 433 break; 434 435 case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: 436 default: 437 location = new SmsCbLocation(plmn); 438 break; 439 } 440 441 byte[][] pdus; 442 int pageCount = header.getNumberOfPages(); 443 if (pageCount > 1) { 444 // Multi-page message 445 SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location); 446 447 // Try to find other pages of the same message 448 pdus = mSmsCbPageMap.get(concatInfo); 449 450 if (pdus == null) { 451 // This is the first page of this message, make room for all 452 // pages and keep until complete 453 pdus = new byte[pageCount][]; 454 455 mSmsCbPageMap.put(concatInfo, pdus); 456 } 457 458 // Page parameter is one-based 459 pdus[header.getPageIndex() - 1] = receivedPdu; 460 461 for (int i = 0; i < pdus.length; i++) { 462 if (pdus[i] == null) { 463 // Still missing pages, exit 464 return; 465 } 466 } 467 468 // Message complete, remove and dispatch 469 mSmsCbPageMap.remove(concatInfo); 470 } else { 471 // Single page message 472 pdus = new byte[1][]; 473 pdus[0] = receivedPdu; 474 } 475 476 SmsCbMessage message = GsmSmsCbMessage.createSmsCbMessage(header, location, pdus); 477 dispatchBroadcastMessage(message); 478 479 // Remove messages that are out of scope to prevent the map from 480 // growing indefinitely, containing incomplete messages that were 481 // never assembled 482 Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator(); 483 484 while (iter.hasNext()) { 485 SmsCbConcatInfo info = iter.next(); 486 487 if (!info.matchesLocation(plmn, lac, cid)) { 488 iter.remove(); 489 } 490 } 491 } catch (RuntimeException e) { 492 Rlog.e(TAG, "Error in decoding SMS CB pdu", e); 493 } 494 } 495} 496