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