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