blob: 995f5b1874b7f4a2947d8d357c5941224b9d3547 [file] [log] [blame]
/*
* 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 + '}';
}
}