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