1/* 2 * Copyright (C) 2013 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.cdma; 18 19import android.app.Activity; 20import android.content.Context; 21import android.content.res.Resources; 22import android.os.Message; 23import android.os.SystemProperties; 24import android.provider.Telephony.Sms.Intents; 25import android.telephony.SmsCbMessage; 26 27import com.android.internal.telephony.CellBroadcastHandler; 28import com.android.internal.telephony.CommandsInterface; 29import com.android.internal.telephony.InboundSmsHandler; 30import com.android.internal.telephony.InboundSmsTracker; 31import com.android.internal.telephony.PhoneBase; 32import com.android.internal.telephony.SmsConstants; 33import com.android.internal.telephony.SmsMessageBase; 34import com.android.internal.telephony.SmsStorageMonitor; 35import com.android.internal.telephony.TelephonyProperties; 36import com.android.internal.telephony.WspTypeDecoder; 37import com.android.internal.telephony.cdma.sms.SmsEnvelope; 38 39import java.util.Arrays; 40 41/** 42 * Subclass of {@link InboundSmsHandler} for 3GPP2 type messages. 43 */ 44public class CdmaInboundSmsHandler extends InboundSmsHandler { 45 46 private final CdmaSMSDispatcher mSmsDispatcher; 47 private final CdmaServiceCategoryProgramHandler mServiceCategoryProgramHandler; 48 49 private byte[] mLastDispatchedSmsFingerprint; 50 private byte[] mLastAcknowledgedSmsFingerprint; 51 52 private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean( 53 com.android.internal.R.bool.config_duplicate_port_omadm_wappush); 54 55 /** 56 * Create a new inbound SMS handler for CDMA. 57 */ 58 private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor, 59 PhoneBase phone, CdmaSMSDispatcher smsDispatcher) { 60 super("CdmaInboundSmsHandler", context, storageMonitor, phone, 61 CellBroadcastHandler.makeCellBroadcastHandler(context, phone)); 62 mSmsDispatcher = smsDispatcher; 63 mServiceCategoryProgramHandler = CdmaServiceCategoryProgramHandler.makeScpHandler(context, 64 phone.mCi); 65 phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null); 66 } 67 68 /** 69 * Unregister for CDMA SMS. 70 */ 71 @Override 72 protected void onQuitting() { 73 mPhone.mCi.unSetOnNewCdmaSms(getHandler()); 74 mCellBroadcastHandler.dispose(); 75 76 if (DBG) log("unregistered for 3GPP2 SMS"); 77 super.onQuitting(); 78 } 79 80 /** 81 * Wait for state machine to enter startup state. We can't send any messages until then. 82 */ 83 public static CdmaInboundSmsHandler makeInboundSmsHandler(Context context, 84 SmsStorageMonitor storageMonitor, PhoneBase phone, CdmaSMSDispatcher smsDispatcher) { 85 CdmaInboundSmsHandler handler = new CdmaInboundSmsHandler(context, storageMonitor, 86 phone, smsDispatcher); 87 handler.start(); 88 return handler; 89 } 90 91 /** 92 * Return whether the device is in Emergency Call Mode (only for 3GPP2). 93 * @return true if the device is in ECM; false otherwise 94 */ 95 private static boolean isInEmergencyCallMode() { 96 String inEcm = SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false"); 97 return "true".equals(inEcm); 98 } 99 100 /** 101 * Return true if this handler is for 3GPP2 messages; false for 3GPP format. 102 * @return true (3GPP2) 103 */ 104 @Override 105 protected boolean is3gpp2() { 106 return true; 107 } 108 109 /** 110 * Process Cell Broadcast, Voicemail Notification, and other 3GPP/3GPP2-specific messages. 111 * @param smsb the SmsMessageBase object from the RIL 112 * @return true if the message was handled here; false to continue processing 113 */ 114 @Override 115 protected int dispatchMessageRadioSpecific(SmsMessageBase smsb) { 116 if (isInEmergencyCallMode()) { 117 return Activity.RESULT_OK; 118 } 119 120 SmsMessage sms = (SmsMessage) smsb; 121 boolean isBroadcastType = (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()); 122 123 // Handle CMAS emergency broadcast messages. 124 if (isBroadcastType) { 125 log("Broadcast type message"); 126 SmsCbMessage cbMessage = sms.parseBroadcastSms(); 127 if (cbMessage != null) { 128 mCellBroadcastHandler.dispatchSmsMessage(cbMessage); 129 } else { 130 loge("error trying to parse broadcast SMS"); 131 } 132 return Intents.RESULT_SMS_HANDLED; 133 } 134 135 // Initialize fingerprint field, and see if we have a network duplicate SMS. 136 mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint(); 137 if (mLastAcknowledgedSmsFingerprint != null && 138 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) { 139 return Intents.RESULT_SMS_HANDLED; 140 } 141 142 // Decode BD stream and set sms variables. 143 sms.parseSms(); 144 int teleService = sms.getTeleService(); 145 146 switch (teleService) { 147 case SmsEnvelope.TELESERVICE_VMN: 148 case SmsEnvelope.TELESERVICE_MWI: 149 // handle voicemail indication 150 handleVoicemailTeleservice(sms); 151 return Intents.RESULT_SMS_HANDLED; 152 153 case SmsEnvelope.TELESERVICE_WMT: 154 case SmsEnvelope.TELESERVICE_WEMT: 155 if (sms.isStatusReportMessage()) { 156 mSmsDispatcher.sendStatusReportMessage(sms); 157 return Intents.RESULT_SMS_HANDLED; 158 } 159 break; 160 161 case SmsEnvelope.TELESERVICE_SCPT: 162 mServiceCategoryProgramHandler.dispatchSmsMessage(sms); 163 return Intents.RESULT_SMS_HANDLED; 164 165 case SmsEnvelope.TELESERVICE_WAP: 166 // handled below, after storage check 167 break; 168 169 default: 170 loge("unsupported teleservice 0x" + Integer.toHexString(teleService)); 171 return Intents.RESULT_SMS_UNSUPPORTED; 172 } 173 174 if (!mStorageMonitor.isStorageAvailable() && 175 sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) { 176 // It's a storable message and there's no storage available. Bail. 177 // (See C.S0015-B v2.0 for a description of "Immediate Display" 178 // messages, which we represent as CLASS_0.) 179 return Intents.RESULT_SMS_OUT_OF_MEMORY; 180 } 181 182 if (SmsEnvelope.TELESERVICE_WAP == teleService) { 183 return processCdmaWapPdu(sms.getUserData(), sms.mMessageRef, 184 sms.getOriginatingAddress(), sms.getTimestampMillis()); 185 } 186 187 return dispatchNormalMessage(smsb); 188 } 189 190 /** 191 * Send an acknowledge message. 192 * @param success indicates that last message was successfully received. 193 * @param result result code indicating any error 194 * @param response callback message sent when operation completes. 195 */ 196 @Override 197 protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) { 198 if (isInEmergencyCallMode()) { 199 return; 200 } 201 202 int causeCode = resultToCause(result); 203 mPhone.mCi.acknowledgeLastIncomingCdmaSms(success, causeCode, response); 204 205 if (causeCode == 0) { 206 mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint; 207 } 208 mLastDispatchedSmsFingerprint = null; 209 } 210 211 /** 212 * Called when the phone changes the default method updates mPhone 213 * mStorageMonitor and mCellBroadcastHandler.updatePhoneObject. 214 * Override if different or other behavior is desired. 215 * 216 * @param phone 217 */ 218 @Override 219 protected void onUpdatePhoneObject(PhoneBase phone) { 220 super.onUpdatePhoneObject(phone); 221 mCellBroadcastHandler.updatePhoneObject(phone); 222 } 223 224 /** 225 * Convert Android result code to CDMA SMS failure cause. 226 * @param rc the Android SMS intent result value 227 * @return 0 for success, or a CDMA SMS failure cause value 228 */ 229 private static int resultToCause(int rc) { 230 switch (rc) { 231 case Activity.RESULT_OK: 232 case Intents.RESULT_SMS_HANDLED: 233 // Cause code is ignored on success. 234 return 0; 235 case Intents.RESULT_SMS_OUT_OF_MEMORY: 236 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE; 237 case Intents.RESULT_SMS_UNSUPPORTED: 238 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID; 239 case Intents.RESULT_SMS_GENERIC_ERROR: 240 default: 241 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM; 242 } 243 } 244 245 /** 246 * Handle {@link SmsEnvelope#TELESERVICE_VMN} and {@link SmsEnvelope#TELESERVICE_MWI}. 247 * @param sms the message to process 248 */ 249 private void handleVoicemailTeleservice(SmsMessage sms) { 250 int voicemailCount = sms.getNumOfVoicemails(); 251 if (DBG) log("Voicemail count=" + voicemailCount); 252 253 // range check 254 if (voicemailCount < 0) { 255 voicemailCount = -1; 256 } else if (voicemailCount > 99) { 257 // C.S0015-B v2, 4.5.12 258 // range: 0-99 259 voicemailCount = 99; 260 } 261 // update voice mail count in phone 262 mPhone.setVoiceMessageCount(voicemailCount); 263 // store voice mail count in preferences 264 storeVoiceMailCount(); 265 } 266 267 /** 268 * Processes inbound messages that are in the WAP-WDP PDU format. See 269 * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format. 270 * WDP segments are gathered until a datagram completes and gets dispatched. 271 * 272 * @param pdu The WAP-WDP PDU segment 273 * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or 274 * {@link Activity#RESULT_OK} if the message has been broadcast 275 * to applications 276 */ 277 private int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address, 278 long timestamp) { 279 int index = 0; 280 281 int msgType = (0xFF & pdu[index++]); 282 if (msgType != 0) { 283 log("Received a WAP SMS which is not WDP. Discard."); 284 return Intents.RESULT_SMS_HANDLED; 285 } 286 int totalSegments = (0xFF & pdu[index++]); // >= 1 287 int segment = (0xFF & pdu[index++]); // >= 0 288 289 if (segment >= totalSegments) { 290 loge("WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1)); 291 return Intents.RESULT_SMS_HANDLED; 292 } 293 294 // Only the first segment contains sourcePort and destination Port 295 int sourcePort = 0; 296 int destinationPort = 0; 297 if (segment == 0) { 298 //process WDP segment 299 sourcePort = (0xFF & pdu[index++]) << 8; 300 sourcePort |= 0xFF & pdu[index++]; 301 destinationPort = (0xFF & pdu[index++]) << 8; 302 destinationPort |= 0xFF & pdu[index++]; 303 // Some carriers incorrectly send duplicate port fields in omadm wap pushes. 304 // If configured, check for that here 305 if (mCheckForDuplicatePortsInOmadmWapPush) { 306 if (checkDuplicatePortOmadmWapPush(pdu, index)) { 307 index = index + 4; // skip duplicate port fields 308 } 309 } 310 } 311 312 // Lookup all other related parts 313 log("Received WAP PDU. Type = " + msgType + ", originator = " + address 314 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort 315 + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments); 316 317 // pass the user data portion of the PDU to the shared handler in SMSDispatcher 318 byte[] userData = new byte[pdu.length - index]; 319 System.arraycopy(pdu, index, userData, 0, pdu.length - index); 320 321 InboundSmsTracker tracker = new InboundSmsTracker(userData, timestamp, destinationPort, 322 true, address, referenceNumber, segment, totalSegments, true); 323 324 return addTrackerToRawTableAndSendMessage(tracker); 325 } 326 327 /** 328 * Optional check to see if the received WapPush is an OMADM notification with erroneous 329 * extra port fields. 330 * - Some carriers make this mistake. 331 * ex: MSGTYPE-TotalSegments-CurrentSegment 332 * -SourcePortDestPort-SourcePortDestPort-OMADM PDU 333 * @param origPdu The WAP-WDP PDU segment 334 * @param index Current Index while parsing the PDU. 335 * @return True if OrigPdu is OmaDM Push Message which has duplicate ports. 336 * False if OrigPdu is NOT OmaDM Push Message which has duplicate ports. 337 */ 338 private static boolean checkDuplicatePortOmadmWapPush(byte[] origPdu, int index) { 339 index += 4; 340 byte[] omaPdu = new byte[origPdu.length - index]; 341 System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length); 342 343 WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu); 344 int wspIndex = 2; 345 346 // Process header length field 347 if (!pduDecoder.decodeUintvarInteger(wspIndex)) { 348 return false; 349 } 350 351 wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field 352 353 // Process content type field 354 if (!pduDecoder.decodeContentType(wspIndex)) { 355 return false; 356 } 357 358 String mimeType = pduDecoder.getValueString(); 359 return (WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI.equals(mimeType)); 360 } 361} 362