/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony.cdma; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.os.Message; import android.os.SystemProperties; import android.provider.Telephony.Sms.Intents; import android.telephony.SmsCbMessage; import com.android.internal.telephony.CellBroadcastHandler; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.InboundSmsHandler; import com.android.internal.telephony.InboundSmsTracker; import com.android.internal.telephony.PhoneBase; import com.android.internal.telephony.SmsConstants; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsStorageMonitor; import com.android.internal.telephony.TelephonyProperties; import com.android.internal.telephony.WspTypeDecoder; import com.android.internal.telephony.cdma.sms.SmsEnvelope; import java.util.Arrays; /** * Subclass of {@link InboundSmsHandler} for 3GPP2 type messages. */ public class CdmaInboundSmsHandler extends InboundSmsHandler { private final CdmaSMSDispatcher mSmsDispatcher; private final CdmaServiceCategoryProgramHandler mServiceCategoryProgramHandler; private byte[] mLastDispatchedSmsFingerprint; private byte[] mLastAcknowledgedSmsFingerprint; private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean( com.android.internal.R.bool.config_duplicate_port_omadm_wappush); /** * Create a new inbound SMS handler for CDMA. */ private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor, PhoneBase phone, CdmaSMSDispatcher smsDispatcher) { super("CdmaInboundSmsHandler", context, storageMonitor, phone, CellBroadcastHandler.makeCellBroadcastHandler(context, phone)); mSmsDispatcher = smsDispatcher; mServiceCategoryProgramHandler = CdmaServiceCategoryProgramHandler.makeScpHandler(context, phone.mCi); phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null); } /** * Unregister for CDMA SMS. */ @Override protected void onQuitting() { mPhone.mCi.unSetOnNewCdmaSms(getHandler()); mCellBroadcastHandler.dispose(); if (DBG) log("unregistered for 3GPP2 SMS"); super.onQuitting(); } /** * Wait for state machine to enter startup state. We can't send any messages until then. */ public static CdmaInboundSmsHandler makeInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor, PhoneBase phone, CdmaSMSDispatcher smsDispatcher) { CdmaInboundSmsHandler handler = new CdmaInboundSmsHandler(context, storageMonitor, phone, smsDispatcher); handler.start(); return handler; } /** * Return whether the device is in Emergency Call Mode (only for 3GPP2). * @return true if the device is in ECM; false otherwise */ private static boolean isInEmergencyCallMode() { String inEcm = SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false"); return "true".equals(inEcm); } /** * Return true if this handler is for 3GPP2 messages; false for 3GPP format. * @return true (3GPP2) */ @Override protected boolean is3gpp2() { return true; } /** * Process Cell Broadcast, Voicemail Notification, and other 3GPP/3GPP2-specific messages. * @param smsb the SmsMessageBase object from the RIL * @return true if the message was handled here; false to continue processing */ @Override protected int dispatchMessageRadioSpecific(SmsMessageBase smsb) { if (isInEmergencyCallMode()) { return Activity.RESULT_OK; } SmsMessage sms = (SmsMessage) smsb; boolean isBroadcastType = (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()); // Handle CMAS emergency broadcast messages. if (isBroadcastType) { log("Broadcast type message"); SmsCbMessage cbMessage = sms.parseBroadcastSms(); if (cbMessage != null) { mCellBroadcastHandler.dispatchSmsMessage(cbMessage); } else { loge("error trying to parse broadcast SMS"); } return Intents.RESULT_SMS_HANDLED; } // Initialize fingerprint field, and see if we have a network duplicate SMS. mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint(); if (mLastAcknowledgedSmsFingerprint != null && Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) { return Intents.RESULT_SMS_HANDLED; } // Decode BD stream and set sms variables. sms.parseSms(); int teleService = sms.getTeleService(); switch (teleService) { case SmsEnvelope.TELESERVICE_VMN: case SmsEnvelope.TELESERVICE_MWI: // handle voicemail indication handleVoicemailTeleservice(sms); return Intents.RESULT_SMS_HANDLED; case SmsEnvelope.TELESERVICE_WMT: case SmsEnvelope.TELESERVICE_WEMT: if (sms.isStatusReportMessage()) { mSmsDispatcher.sendStatusReportMessage(sms); return Intents.RESULT_SMS_HANDLED; } break; case SmsEnvelope.TELESERVICE_SCPT: mServiceCategoryProgramHandler.dispatchSmsMessage(sms); return Intents.RESULT_SMS_HANDLED; case SmsEnvelope.TELESERVICE_WAP: // handled below, after storage check break; default: loge("unsupported teleservice 0x" + Integer.toHexString(teleService)); return Intents.RESULT_SMS_UNSUPPORTED; } if (!mStorageMonitor.isStorageAvailable() && sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) { // It's a storable message and there's no storage available. Bail. // (See C.S0015-B v2.0 for a description of "Immediate Display" // messages, which we represent as CLASS_0.) return Intents.RESULT_SMS_OUT_OF_MEMORY; } if (SmsEnvelope.TELESERVICE_WAP == teleService) { return processCdmaWapPdu(sms.getUserData(), sms.mMessageRef, sms.getOriginatingAddress(), sms.getTimestampMillis()); } return dispatchNormalMessage(smsb); } /** * Send an acknowledge message. * @param success indicates that last message was successfully received. * @param result result code indicating any error * @param response callback message sent when operation completes. */ @Override protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) { if (isInEmergencyCallMode()) { return; } int causeCode = resultToCause(result); mPhone.mCi.acknowledgeLastIncomingCdmaSms(success, causeCode, response); if (causeCode == 0) { mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint; } mLastDispatchedSmsFingerprint = null; } /** * Called when the phone changes the default method updates mPhone * mStorageMonitor and mCellBroadcastHandler.updatePhoneObject. * Override if different or other behavior is desired. * * @param phone */ @Override protected void onUpdatePhoneObject(PhoneBase phone) { super.onUpdatePhoneObject(phone); mCellBroadcastHandler.updatePhoneObject(phone); } /** * Convert Android result code to CDMA SMS failure cause. * @param rc the Android SMS intent result value * @return 0 for success, or a CDMA SMS failure cause value */ private static int resultToCause(int rc) { switch (rc) { case Activity.RESULT_OK: case Intents.RESULT_SMS_HANDLED: // Cause code is ignored on success. return 0; case Intents.RESULT_SMS_OUT_OF_MEMORY: return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE; case Intents.RESULT_SMS_UNSUPPORTED: return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID; case Intents.RESULT_SMS_GENERIC_ERROR: default: return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM; } } /** * Handle {@link SmsEnvelope#TELESERVICE_VMN} and {@link SmsEnvelope#TELESERVICE_MWI}. * @param sms the message to process */ private void handleVoicemailTeleservice(SmsMessage sms) { int voicemailCount = sms.getNumOfVoicemails(); if (DBG) log("Voicemail count=" + voicemailCount); // range check if (voicemailCount < 0) { voicemailCount = -1; } else if (voicemailCount > 99) { // C.S0015-B v2, 4.5.12 // range: 0-99 voicemailCount = 99; } // update voice mail count in phone mPhone.setVoiceMessageCount(voicemailCount); // store voice mail count in preferences storeVoiceMailCount(); } /** * Processes inbound messages that are in the WAP-WDP PDU format. See * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format. * WDP segments are gathered until a datagram completes and gets dispatched. * * @param pdu The WAP-WDP PDU segment * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or * {@link Activity#RESULT_OK} if the message has been broadcast * to applications */ private int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address, long timestamp) { int index = 0; int msgType = (0xFF & pdu[index++]); if (msgType != 0) { log("Received a WAP SMS which is not WDP. Discard."); return Intents.RESULT_SMS_HANDLED; } int totalSegments = (0xFF & pdu[index++]); // >= 1 int segment = (0xFF & pdu[index++]); // >= 0 if (segment >= totalSegments) { loge("WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1)); return Intents.RESULT_SMS_HANDLED; } // Only the first segment contains sourcePort and destination Port int sourcePort = 0; int destinationPort = 0; if (segment == 0) { //process WDP segment sourcePort = (0xFF & pdu[index++]) << 8; sourcePort |= 0xFF & pdu[index++]; destinationPort = (0xFF & pdu[index++]) << 8; destinationPort |= 0xFF & pdu[index++]; // Some carriers incorrectly send duplicate port fields in omadm wap pushes. // If configured, check for that here if (mCheckForDuplicatePortsInOmadmWapPush) { if (checkDuplicatePortOmadmWapPush(pdu, index)) { index = index + 4; // skip duplicate port fields } } } // Lookup all other related parts log("Received WAP PDU. Type = " + msgType + ", originator = " + address + ", src-port = " + sourcePort + ", dst-port = " + destinationPort + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments); // pass the user data portion of the PDU to the shared handler in SMSDispatcher byte[] userData = new byte[pdu.length - index]; System.arraycopy(pdu, index, userData, 0, pdu.length - index); InboundSmsTracker tracker = new InboundSmsTracker(userData, timestamp, destinationPort, true, address, referenceNumber, segment, totalSegments, true); return addTrackerToRawTableAndSendMessage(tracker); } /** * Optional check to see if the received WapPush is an OMADM notification with erroneous * extra port fields. * - Some carriers make this mistake. * ex: MSGTYPE-TotalSegments-CurrentSegment * -SourcePortDestPort-SourcePortDestPort-OMADM PDU * @param origPdu The WAP-WDP PDU segment * @param index Current Index while parsing the PDU. * @return True if OrigPdu is OmaDM Push Message which has duplicate ports. * False if OrigPdu is NOT OmaDM Push Message which has duplicate ports. */ private static boolean checkDuplicatePortOmadmWapPush(byte[] origPdu, int index) { index += 4; byte[] omaPdu = new byte[origPdu.length - index]; System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length); WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu); int wspIndex = 2; // Process header length field if (!pduDecoder.decodeUintvarInteger(wspIndex)) { return false; } wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field // Process content type field if (!pduDecoder.decodeContentType(wspIndex)) { return false; } String mimeType = pduDecoder.getValueString(); return (WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI.equals(mimeType)); } }