GsmSMSDispatcher.java revision b49a73dfc4c9817bba1f227e9330555acdf9b56f
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.provider.Telephony.Sms; 26import android.provider.Telephony.Sms.Intents; 27import android.telephony.ServiceState; 28import android.util.Config; 29import android.util.Log; 30 31import com.android.internal.telephony.CommandsInterface; 32import com.android.internal.telephony.IccUtils; 33import com.android.internal.telephony.SMSDispatcher; 34import com.android.internal.telephony.SmsHeader; 35import com.android.internal.telephony.SmsMessageBase; 36import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; 37 38import java.util.ArrayList; 39import java.util.HashMap; 40 41import static android.telephony.SmsMessage.MessageClass; 42 43final class GsmSMSDispatcher extends SMSDispatcher { 44 private static final String TAG = "GSM"; 45 46 private GSMPhone mGsmPhone; 47 48 GsmSMSDispatcher(GSMPhone phone) { 49 super(phone); 50 mGsmPhone = phone; 51 } 52 53 /** 54 * Called when a status report is received. This should correspond to 55 * a previously successful SEND. 56 * 57 * @param ar AsyncResult passed into the message handler. ar.result should 58 * be a String representing the status report PDU, as ASCII hex. 59 */ 60 protected void handleStatusReport(AsyncResult ar) { 61 String pduString = (String) ar.result; 62 SmsMessage sms = SmsMessage.newFromCDS(pduString); 63 64 if (sms != null) { 65 int tpStatus = sms.getStatus(); 66 int messageRef = sms.messageRef; 67 for (int i = 0, count = deliveryPendingList.size(); i < count; i++) { 68 SmsTracker tracker = deliveryPendingList.get(i); 69 if (tracker.mMessageRef == messageRef) { 70 // Found it. Remove from list and broadcast. 71 if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) { 72 deliveryPendingList.remove(i); 73 } 74 PendingIntent intent = tracker.mDeliveryIntent; 75 Intent fillIn = new Intent(); 76 fillIn.putExtra("pdu", IccUtils.hexStringToBytes(pduString)); 77 try { 78 intent.send(mContext, Activity.RESULT_OK, fillIn); 79 } catch (CanceledException ex) {} 80 81 // Only expect to see one tracker matching this messageref 82 break; 83 } 84 } 85 } 86 acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null); 87 } 88 89 90 /** {@inheritDoc} */ 91 protected int dispatchMessage(SmsMessageBase smsb) { 92 93 // If sms is null, means there was a parsing error. 94 if (smsb == null) { 95 return Intents.RESULT_SMS_GENERIC_ERROR; 96 } 97 SmsMessage sms = (SmsMessage) smsb; 98 boolean handled = false; 99 100 if (sms.isTypeZero()) { 101 // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be 102 // Displayed/Stored/Notified. They should only be acknowledged. 103 Log.d(TAG, "Received short message type 0, Don't display or store it. Send Ack"); 104 return Intents.RESULT_SMS_HANDLED; 105 } 106 107 // Special case the message waiting indicator messages 108 if (sms.isMWISetMessage()) { 109 mGsmPhone.updateMessageWaitingIndicator(true); 110 handled = sms.isMwiDontStore(); 111 if (Config.LOGD) { 112 Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled); 113 } 114 } else if (sms.isMWIClearMessage()) { 115 mGsmPhone.updateMessageWaitingIndicator(false); 116 handled = sms.isMwiDontStore(); 117 if (Config.LOGD) { 118 Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled); 119 } 120 } 121 122 if (handled) { 123 return Intents.RESULT_SMS_HANDLED; 124 } 125 126 if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) { 127 // It's a storable message and there's no storage available. Bail. 128 // (See TS 23.038 for a description of class 0 messages.) 129 return Intents.RESULT_SMS_OUT_OF_MEMORY; 130 } 131 132 SmsHeader smsHeader = sms.getUserDataHeader(); 133 // See if message is partial or port addressed. 134 if ((smsHeader == null) || (smsHeader.concatRef == null)) { 135 // Message is not partial (not part of concatenated sequence). 136 byte[][] pdus = new byte[1][]; 137 pdus[0] = sms.getPdu(); 138 139 if (smsHeader != null && smsHeader.portAddrs != null) { 140 if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) { 141 return mWapPush.dispatchWapPdu(sms.getUserData()); 142 } else { 143 // The message was sent to a port, so concoct a URI for it. 144 dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort); 145 } 146 } else { 147 // Normal short and non-port-addressed message, dispatch it. 148 dispatchPdus(pdus); 149 } 150 return Activity.RESULT_OK; 151 } else { 152 // Process the message part. 153 return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs); 154 } 155 } 156 157 /** {@inheritDoc} */ 158 protected void sendData(String destAddr, String scAddr, int destPort, 159 byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { 160 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( 161 scAddr, destAddr, destPort, data, (deliveryIntent != null)); 162 sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent); 163 } 164 165 /** {@inheritDoc} */ 166 protected void sendText(String destAddr, String scAddr, String text, 167 PendingIntent sentIntent, PendingIntent deliveryIntent) { 168 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( 169 scAddr, destAddr, text, (deliveryIntent != null)); 170 sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent); 171 } 172 173 /** {@inheritDoc} */ 174 protected void sendMultipartText(String destinationAddress, String scAddress, 175 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, 176 ArrayList<PendingIntent> deliveryIntents) { 177 178 int refNumber = getNextConcatenatedRef() & 0x00FF; 179 int msgCount = parts.size(); 180 int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN; 181 182 mRemainingMessages = msgCount; 183 184 TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount]; 185 for (int i = 0; i < msgCount; i++) { 186 TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false); 187 if (encoding != details.codeUnitSize 188 && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN 189 || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) { 190 encoding = details.codeUnitSize; 191 } 192 encodingForParts[i] = details; 193 } 194 195 for (int i = 0; i < msgCount; i++) { 196 SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); 197 concatRef.refNumber = refNumber; 198 concatRef.seqNumber = i + 1; // 1-based sequence 199 concatRef.msgCount = msgCount; 200 // TODO: We currently set this to true since our messaging app will never 201 // send more than 255 parts (it converts the message to MMS well before that). 202 // However, we should support 3rd party messaging apps that might need 16-bit 203 // references 204 // Note: It's not sufficient to just flip this bit to true; it will have 205 // ripple effects (several calculations assume 8-bit ref). 206 concatRef.isEightBits = true; 207 SmsHeader smsHeader = new SmsHeader(); 208 smsHeader.concatRef = concatRef; 209 if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) { 210 smsHeader.languageTable = encodingForParts[i].languageTable; 211 smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable; 212 } 213 214 PendingIntent sentIntent = null; 215 if (sentIntents != null && sentIntents.size() > i) { 216 sentIntent = sentIntents.get(i); 217 } 218 219 PendingIntent deliveryIntent = null; 220 if (deliveryIntents != null && deliveryIntents.size() > i) { 221 deliveryIntent = deliveryIntents.get(i); 222 } 223 224 SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress, 225 parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader), 226 encoding, smsHeader.languageTable, smsHeader.languageShiftTable); 227 228 sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent); 229 } 230 } 231 232 /** 233 * Send a multi-part text based SMS which already passed SMS control check. 234 * 235 * It is the working function for sendMultipartText(). 236 * 237 * @param destinationAddress the address to send the message to 238 * @param scAddress is the service center address or null to use 239 * the current default SMSC 240 * @param parts an <code>ArrayList</code> of strings that, in order, 241 * comprise the original message 242 * @param sentIntents if not null, an <code>ArrayList</code> of 243 * <code>PendingIntent</code>s (one for each message part) that is 244 * broadcast when the corresponding message part has been sent. 245 * The result code will be <code>Activity.RESULT_OK<code> for success, 246 * or one of these errors: 247 * <code>RESULT_ERROR_GENERIC_FAILURE</code> 248 * <code>RESULT_ERROR_RADIO_OFF</code> 249 * <code>RESULT_ERROR_NULL_PDU</code>. 250 * @param deliveryIntents 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 delivered 253 * to the recipient. The raw pdu of the status report is in the 254 * extended data ("pdu"). 255 */ 256 private void sendMultipartTextWithPermit(String destinationAddress, 257 String scAddress, ArrayList<String> parts, 258 ArrayList<PendingIntent> sentIntents, 259 ArrayList<PendingIntent> deliveryIntents) { 260 261 // check if in service 262 int ss = mPhone.getServiceState().getState(); 263 if (ss != ServiceState.STATE_IN_SERVICE) { 264 for (int i = 0, count = parts.size(); i < count; i++) { 265 PendingIntent sentIntent = null; 266 if (sentIntents != null && sentIntents.size() > i) { 267 sentIntent = sentIntents.get(i); 268 } 269 SmsTracker tracker = SmsTrackerFactory(null, sentIntent, null); 270 handleNotInService(ss, tracker); 271 } 272 return; 273 } 274 275 int refNumber = getNextConcatenatedRef() & 0x00FF; 276 int msgCount = parts.size(); 277 int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN; 278 279 mRemainingMessages = msgCount; 280 281 TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount]; 282 for (int i = 0; i < msgCount; i++) { 283 TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false); 284 if (encoding != details.codeUnitSize 285 && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN 286 || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) { 287 encoding = details.codeUnitSize; 288 } 289 encodingForParts[i] = details; 290 } 291 292 for (int i = 0; i < msgCount; i++) { 293 SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); 294 concatRef.refNumber = refNumber; 295 concatRef.seqNumber = i + 1; // 1-based sequence 296 concatRef.msgCount = msgCount; 297 concatRef.isEightBits = false; 298 SmsHeader smsHeader = new SmsHeader(); 299 smsHeader.concatRef = concatRef; 300 if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) { 301 smsHeader.languageTable = encodingForParts[i].languageTable; 302 smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable; 303 } 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, smsHeader.languageTable, smsHeader.languageShiftTable); 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 protected void sendSms(SmsTracker tracker) { 330 HashMap map = tracker.mData; 331 332 byte smsc[] = (byte[]) map.get("smsc"); 333 byte pdu[] = (byte[]) map.get("pdu"); 334 335 Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker); 336 mCm.sendSMS(IccUtils.bytesToHexString(smsc), 337 IccUtils.bytesToHexString(pdu), reply); 338 } 339 340 /** 341 * Send the multi-part SMS based on multipart Sms tracker 342 * 343 * @param tracker holds the multipart Sms tracker ready to be sent 344 */ 345 protected void sendMultipartSms (SmsTracker tracker) { 346 ArrayList<String> parts; 347 ArrayList<PendingIntent> sentIntents; 348 ArrayList<PendingIntent> deliveryIntents; 349 350 HashMap map = tracker.mData; 351 352 String destinationAddress = (String) map.get("destination"); 353 String scAddress = (String) map.get("scaddress"); 354 355 parts = (ArrayList<String>) map.get("parts"); 356 sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents"); 357 deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents"); 358 359 sendMultipartTextWithPermit(destinationAddress, 360 scAddress, parts, sentIntents, deliveryIntents); 361 362 } 363 364 /** {@inheritDoc} */ 365 protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){ 366 // FIXME unit test leaves cm == null. this should change 367 if (mCm != null) { 368 mCm.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response); 369 } 370 } 371 372 /** {@inheritDoc} */ 373 protected void activateCellBroadcastSms(int activate, Message response) { 374 // Unless CBS is implemented for GSM, this point should be unreachable. 375 Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM."); 376 response.recycle(); 377 } 378 379 /** {@inheritDoc} */ 380 protected void getCellBroadcastSmsConfig(Message response){ 381 // Unless CBS is implemented for GSM, this point should be unreachable. 382 Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM."); 383 response.recycle(); 384 } 385 386 /** {@inheritDoc} */ 387 protected void setCellBroadcastConfig(int[] configValuesArray, Message response) { 388 // Unless CBS is implemented for GSM, this point should be unreachable. 389 Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM."); 390 response.recycle(); 391 } 392 393 private int resultToCause(int rc) { 394 switch (rc) { 395 case Activity.RESULT_OK: 396 case Intents.RESULT_SMS_HANDLED: 397 // Cause code is ignored on success. 398 return 0; 399 case Intents.RESULT_SMS_OUT_OF_MEMORY: 400 return CommandsInterface.GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED; 401 case Intents.RESULT_SMS_GENERIC_ERROR: 402 default: 403 return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR; 404 } 405 } 406} 407