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