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