/* * Copyright (C) 2010 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.gsm; import android.telephony.SmsCbCmasInfo; import android.telephony.SmsCbEtwsInfo; import java.util.Arrays; /** * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by * CellBroadcastReceiver test cases, but should not be used by applications. * * All relevant header information is now sent as a Parcelable * {@link android.telephony.SmsCbMessage} object in the "message" extra of the * {@link android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION} or * {@link android.provider.Telephony.Sms.Intents#SMS_EMERGENCY_CB_RECEIVED_ACTION} intent. * The raw PDU is no longer sent to SMS CB applications. */ class SmsCbHeader { /** * Length of SMS-CB header */ static final int PDU_HEADER_LENGTH = 6; /** * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1 */ static final int FORMAT_GSM = 1; /** * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2 */ static final int FORMAT_UMTS = 2; /** * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3 */ static final int FORMAT_ETWS_PRIMARY = 3; /** * Message type value as defined in 3gpp TS 25.324, section 11.1. */ private static final int MESSAGE_TYPE_CBS_MESSAGE = 1; /** * Length of GSM pdus */ private static final int PDU_LENGTH_GSM = 88; /** * Maximum length of ETWS primary message GSM pdus */ private static final int PDU_LENGTH_ETWS = 56; private final int geographicalScope; /** The serial number combines geographical scope, message code, and update number. */ private final int serialNumber; /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */ private final int messageIdentifier; private final int dataCodingScheme; private final int pageIndex; private final int nrOfPages; private final int format; /** ETWS warning notification info. */ private final SmsCbEtwsInfo mEtwsInfo; /** CMAS warning notification info. */ private final SmsCbCmasInfo mCmasInfo; public SmsCbHeader(byte[] pdu) throws IllegalArgumentException { if (pdu == null || pdu.length < PDU_HEADER_LENGTH) { throw new IllegalArgumentException("Illegal PDU"); } if (pdu.length <= PDU_LENGTH_GSM) { // can be ETWS or GSM format. // Per TS23.041 9.4.1.2 and 9.4.1.3.2, GSM and ETWS format both // contain serial number which contains GS, Message Code, and Update Number // per 9.4.1.2.1, and message identifier in same octets geographicalScope = (pdu[0] & 0xc0) >>> 6; serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff); messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff); if (isEtwsMessage() && pdu.length <= PDU_LENGTH_ETWS) { format = FORMAT_ETWS_PRIMARY; dataCodingScheme = -1; pageIndex = -1; nrOfPages = -1; boolean emergencyUserAlert = (pdu[4] & 0x1) != 0; boolean activatePopup = (pdu[5] & 0x80) != 0; int warningType = (pdu[4] & 0xfe) >>> 1; byte[] warningSecurityInfo; // copy the Warning-Security-Information, if present if (pdu.length > PDU_HEADER_LENGTH) { warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length); } else { warningSecurityInfo = null; } mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup, warningSecurityInfo); mCmasInfo = null; return; // skip the ETWS/CMAS initialization code for regular notifications } else { // GSM pdus are no more than 88 bytes format = FORMAT_GSM; dataCodingScheme = pdu[4] & 0xff; // Check for invalid page parameter int pageIndex = (pdu[5] & 0xf0) >>> 4; int nrOfPages = pdu[5] & 0x0f; if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) { pageIndex = 1; nrOfPages = 1; } this.pageIndex = pageIndex; this.nrOfPages = nrOfPages; } } else { // UMTS pdus are always at least 90 bytes since the payload includes // a number-of-pages octet and also one length octet per page format = FORMAT_UMTS; int messageType = pdu[0]; if (messageType != MESSAGE_TYPE_CBS_MESSAGE) { throw new IllegalArgumentException("Unsupported message type " + messageType); } messageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff; geographicalScope = (pdu[3] & 0xc0) >>> 6; serialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff); dataCodingScheme = pdu[5] & 0xff; // We will always consider a UMTS message as having one single page // since there's only one instance of the header, even though the // actual payload may contain several pages. pageIndex = 1; nrOfPages = 1; } if (isEtwsMessage()) { boolean emergencyUserAlert = isEtwsEmergencyUserAlert(); boolean activatePopup = isEtwsPopupAlert(); int warningType = getEtwsWarningType(); mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup, null); mCmasInfo = null; } else if (isCmasMessage()) { int messageClass = getCmasMessageClass(); int severity = getCmasSeverity(); int urgency = getCmasUrgency(); int certainty = getCmasCertainty(); mEtwsInfo = null; mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty); } else { mEtwsInfo = null; mCmasInfo = null; } } int getGeographicalScope() { return geographicalScope; } int getSerialNumber() { return serialNumber; } int getServiceCategory() { return messageIdentifier; } int getDataCodingScheme() { return dataCodingScheme; } int getPageIndex() { return pageIndex; } int getNumberOfPages() { return nrOfPages; } SmsCbEtwsInfo getEtwsInfo() { return mEtwsInfo; } SmsCbCmasInfo getCmasInfo() { return mCmasInfo; } /** * Return whether this broadcast is an emergency (PWS) message type. * @return true if this message is emergency type; false otherwise */ boolean isEmergencyMessage() { return messageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER && messageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER; } /** * Return whether this broadcast is an ETWS emergency message type. * @return true if this message is ETWS emergency type; false otherwise */ private boolean isEtwsMessage() { return (messageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK) == SmsCbConstants.MESSAGE_ID_ETWS_TYPE; } /** * Return whether this broadcast is an ETWS primary notification. * @return true if this message is an ETWS primary notification; false otherwise */ boolean isEtwsPrimaryNotification() { return format == FORMAT_ETWS_PRIMARY; } /** * Return whether this broadcast is in UMTS format. * @return true if this message is in UMTS format; false otherwise */ boolean isUmtsFormat() { return format == FORMAT_UMTS; } /** * Return whether this message is a CMAS emergency message type. * @return true if this message is CMAS emergency type; false otherwise */ private boolean isCmasMessage() { return messageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER && messageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER; } /** * Return whether the popup alert flag is set for an ETWS warning notification. * This method assumes that the message ID has already been checked for ETWS type. * * @return true if the message code indicates a popup alert should be displayed */ private boolean isEtwsPopupAlert() { return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0; } /** * Return whether the emergency user alert flag is set for an ETWS warning notification. * This method assumes that the message ID has already been checked for ETWS type. * * @return true if the message code indicates an emergency user alert */ private boolean isEtwsEmergencyUserAlert() { return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0; } /** * Returns the warning type for an ETWS warning notification. * This method assumes that the message ID has already been checked for ETWS type. * * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24 */ private int getEtwsWarningType() { return messageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING; } /** * Returns the message class for a CMAS warning notification. * This method assumes that the message ID has already been checked for CMAS type. * @return the CMAS message class as defined in {@link SmsCbCmasInfo} */ private int getCmasMessageClass() { switch (messageIdentifier) { case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL: return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY: return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST: return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE: return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE: return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE; default: return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; } } /** * Returns the severity for a CMAS warning notification. This is only available for extreme * and severe alerts, not for other types such as Presidential Level and AMBER alerts. * This method assumes that the message ID has already been checked for CMAS type. * @return the CMAS severity as defined in {@link SmsCbCmasInfo} */ private int getCmasSeverity() { switch (messageIdentifier) { case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE; default: return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; } } /** * Returns the urgency for a CMAS warning notification. This is only available for extreme * and severe alerts, not for other types such as Presidential Level and AMBER alerts. * This method assumes that the message ID has already been checked for CMAS type. * @return the CMAS urgency as defined in {@link SmsCbCmasInfo} */ private int getCmasUrgency() { switch (messageIdentifier) { case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED; default: return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; } } /** * Returns the certainty for a CMAS warning notification. This is only available for extreme * and severe alerts, not for other types such as Presidential Level and AMBER alerts. * This method assumes that the message ID has already been checked for CMAS type. * @return the CMAS certainty as defined in {@link SmsCbCmasInfo} */ private int getCmasCertainty() { switch (messageIdentifier) { case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY; default: return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; } } @Override public String toString() { return "SmsCbHeader{GS=" + geographicalScope + ", serialNumber=0x" + Integer.toHexString(serialNumber) + ", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) + ", DCS=0x" + Integer.toHexString(dataCodingScheme) + ", page " + pageIndex + " of " + nrOfPages + '}'; } }