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