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