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