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