Revert "Add support for CMAS warning notifications over CDMA." I'll submit again when the app change is ready.

This reverts commit 0c49f03a04
This commit is contained in:
Jake Hamby
2012-02-29 14:00:12 -08:00
committed by Android (Google) Code Review
parent 0c49f03a04
commit efba344b5a
20 changed files with 609 additions and 2929 deletions

View File

@ -39,7 +39,6 @@ import android.os.SystemProperties;
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
import android.provider.Settings;
import android.telephony.SmsCbMessage;
import android.telephony.SmsMessage;
import android.telephony.ServiceState;
import android.util.Log;
@ -1079,16 +1078,16 @@ public abstract class SMSDispatcher extends Handler {
}
};
protected void dispatchBroadcastMessage(SmsCbMessage message) {
if (message.isEmergencyMessage()) {
protected void dispatchBroadcastPdus(byte[][] pdus, boolean isEmergencyMessage) {
if (isEmergencyMessage) {
Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
intent.putExtra("message", message);
Log.d(TAG, "Dispatching emergency SMS CB");
intent.putExtra("pdus", pdus);
Log.d(TAG, "Dispatching " + pdus.length + " emergency SMS CB pdus");
dispatch(intent, RECEIVE_EMERGENCY_BROADCAST_PERMISSION);
} else {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
intent.putExtra("message", message);
Log.d(TAG, "Dispatching SMS CB");
intent.putExtra("pdus", pdus);
Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus");
dispatch(intent, RECEIVE_SMS_PERMISSION);
}
}

View File

@ -30,7 +30,6 @@ import android.os.SystemProperties;
import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
import android.telephony.SmsCbMessage;
import android.telephony.SmsManager;
import android.telephony.SmsMessage.MessageClass;
import android.util.Log;
@ -98,10 +97,6 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
}
}
private void handleServiceCategoryProgramData(SmsMessage sms) {
}
/** {@inheritDoc} */
@Override
public int dispatchMessage(SmsMessageBase smsb) {
@ -124,19 +119,8 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
return Intents.RESULT_SMS_HANDLED;
}
SmsMessage sms = (SmsMessage) smsb;
// Handle CMAS emergency broadcast messages.
if (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()) {
Log.d(TAG, "Broadcast type message");
SmsCbMessage message = sms.parseBroadcastSms();
if (message != null) {
dispatchBroadcastMessage(message);
}
return Intents.RESULT_SMS_HANDLED;
}
// See if we have a network duplicate SMS.
SmsMessage sms = (SmsMessage) smsb;
mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
if (mLastAcknowledgedSmsFingerprint != null &&
Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
@ -165,9 +149,6 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
sms.isStatusReportMessage()) {
handleCdmaStatusReport(sms);
handled = true;
} else if (SmsEnvelope.TELESERVICE_SCPT == teleService) {
handleServiceCategoryProgramData(sms);
handled = true;
} else if ((sms.getUserData() == null)) {
if (false) {
Log.d(TAG, "Received SMS without user data");

View File

@ -19,10 +19,7 @@ package com.android.internal.telephony.cdma;
import android.os.Parcel;
import android.os.SystemProperties;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
import android.util.Log;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
@ -42,8 +39,6 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import static android.telephony.SmsMessage.MessageClass;
/**
* TODO(cleanup): these constants are disturbing... are they not just
* different interpretations on one number? And if we did not have
@ -52,6 +47,12 @@ import static android.telephony.SmsMessage.MessageClass;
* named CdmaSmsMessage, could it not?
*/
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS;
import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER;
import static android.telephony.SmsMessage.MessageClass;
/**
* TODO(cleanup): internally returning null in many places makes
* debugging very hard (among many other reasons) and should be made
@ -191,17 +192,16 @@ public class SmsMessage extends SmsMessageBase {
// bearer data
countInt = p.readInt(); //p_cur->uBearerDataLen
if (countInt < 0) {
countInt = 0;
if (countInt >0) {
data = new byte[countInt];
//p_cur->aBearerData[digitCount] :
for (int index=0; index < countInt; index++) {
data[index] = p.readByte();
}
env.bearerData = data;
// BD gets further decoded when accessed in SMSDispatcher
}
data = new byte[countInt];
for (int index=0; index < countInt; index++) {
data[index] = p.readByte();
}
// BD gets further decoded when accessed in SMSDispatcher
env.bearerData = data;
// link the the filled objects to the SMS
env.origAddress = addr;
env.origSubaddress = subaddr;
@ -731,29 +731,6 @@ public class SmsMessage extends SmsMessageBase {
}
}
/**
* Parses a broadcast SMS, possibly containing a CMAS alert.
*/
SmsCbMessage parseBroadcastSms() {
BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
if (bData == null) {
Log.w(LOG_TAG, "BearerData.decode() returned null");
return null;
}
if (Log.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
Log.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
}
String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
SmsCbLocation location = new SmsCbLocation(plmn);
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location,
mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr,
bData.priority, null, bData.cmasWarningInfo);
}
/**
* {@inheritDoc}
*/

View File

@ -16,30 +16,29 @@
package com.android.internal.telephony.cdma.sms;
import android.content.res.Resources;
import android.telephony.SmsCbCmasInfo;
import android.telephony.SmsCbMessage;
import android.telephony.SmsMessage;
import android.telephony.cdma.CdmaSmsCbProgramData;
import android.text.format.Time;
import android.util.Log;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
import com.android.internal.util.BitwiseInputStream;
import com.android.internal.util.BitwiseOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.TimeZone;
import static android.telephony.SmsMessage.ENCODING_16BIT;
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
import android.util.Log;
import android.telephony.SmsMessage;
import android.text.format.Time;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
import com.android.internal.util.BitwiseInputStream;
import com.android.internal.util.BitwiseOutputStream;
import android.content.res.Resources;
import java.util.TimeZone;
/**
* An object to encode and decode CDMA SMS bearer data.
*/
@ -69,8 +68,8 @@ public final class BearerData {
private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE = 0x0F;
//private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA = 0x10;
private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX = 0x11;
private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12;
private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13;
//private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12;
//private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13;
private final static byte SUBPARAM_MESSAGE_STATUS = 0x14;
//private final static byte SUBPARAM_TP_FAILURE_CAUSE = 0x15;
//private final static byte SUBPARAM_ENHANCED_VMN = 0x16;
@ -340,68 +339,12 @@ public final class BearerData {
*/
public CdmaSmsAddress callbackNumber;
/**
* CMAS warning notification information.
* @see #decodeCmasUserData(BearerData, int)
*/
public SmsCbCmasInfo cmasWarningInfo;
/**
* The Service Category Program Data subparameter is used to enable and disable
* SMS broadcast service categories to display. If this subparameter is present,
* this field will contain a list of one or more
* {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the
* operation(s) to perform.
*/
public List<CdmaSmsCbProgramData> serviceCategoryProgramData;
private static class CodingException extends Exception {
public CodingException(String s) {
super(s);
}
}
/**
* Returns the language indicator as a two-character ISO 639 string.
* @return a two character ISO 639 language code
*/
public String getLanguage() {
return getLanguageCodeForValue(language);
}
/**
* Converts a CDMA language indicator value to an ISO 639 two character language code.
* @param languageValue the CDMA language value to convert
* @return the two character ISO 639 language code for the specified value, or null if unknown
*/
private static String getLanguageCodeForValue(int languageValue) {
switch (languageValue) {
case LANGUAGE_ENGLISH:
return "en";
case LANGUAGE_FRENCH:
return "fr";
case LANGUAGE_SPANISH:
return "es";
case LANGUAGE_JAPANESE:
return "ja";
case LANGUAGE_KOREAN:
return "ko";
case LANGUAGE_CHINESE:
return "zh";
case LANGUAGE_HEBREW:
return "he";
default:
return null;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@ -973,9 +916,6 @@ public final class BearerData {
private static String decodeUtf8(byte[] data, int offset, int numFields)
throws CodingException
{
if (numFields < 0 || (numFields + offset) > data.length) {
throw new CodingException("UTF-8 decode failed: offset or length out of range");
}
try {
return new String(data, offset, numFields, "UTF-8");
} catch (java.io.UnsupportedEncodingException ex) {
@ -986,12 +926,11 @@ public final class BearerData {
private static String decodeUtf16(byte[] data, int offset, int numFields)
throws CodingException
{
int byteCount = numFields * 2;
if (byteCount < 0 || (byteCount + offset) > data.length) {
throw new CodingException("UTF-16 decode failed: offset or length out of range");
}
// Start reading from the next 16-bit aligned boundary after offset.
int padding = offset % 2;
numFields -= (offset + padding) / 2;
try {
return new String(data, offset, byteCount, "utf-16be");
return new String(data, offset, numFields * 2, "utf-16be");
} catch (java.io.UnsupportedEncodingException ex) {
throw new CodingException("UTF-16 decode failed: " + ex);
}
@ -1049,11 +988,8 @@ public final class BearerData {
private static String decodeLatin(byte[] data, int offset, int numFields)
throws CodingException
{
if (numFields < 0 || (numFields + offset) > data.length) {
throw new CodingException("ISO-8859-1 decode failed: offset or length out of range");
}
try {
return new String(data, offset, numFields, "ISO-8859-1");
return new String(data, offset, numFields - offset, "ISO-8859-1");
} catch (java.io.UnsupportedEncodingException ex) {
throw new CodingException("ISO-8859-1 decode failed: " + ex);
}
@ -1096,7 +1032,6 @@ public final class BearerData {
userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields);
}
break;
case UserData.ENCODING_IA5:
case UserData.ENCODING_7BIT_ASCII:
userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields);
@ -1179,9 +1114,8 @@ public final class BearerData {
BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
int dataLen = inStream.available() / 6; // 6-bit packed character encoding.
int numFields = bData.userData.numFields;
// dataLen may be > 14 characters due to octet padding
if ((numFields > 14) || (dataLen < numFields)) {
throw new CodingException("IS-91 short message decoding failed");
if ((dataLen > 14) || (dataLen < numFields)) {
throw new CodingException("IS-91 voicemail status decoding failed");
}
StringBuffer strbuf = new StringBuffer(dataLen);
for (int i = 0; i < numFields; i++) {
@ -1605,7 +1539,7 @@ public final class BearerData {
bData.userResponseCode = inStream.read(8);
}
if ((! decodeSuccess) || (paramBits > 0)) {
Log.d(LOG_TAG, "USER_RESPONSE_CODE decode " +
Log.d(LOG_TAG, "USER_REPONSE_CODE decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
}
@ -1614,240 +1548,27 @@ public final class BearerData {
return decodeSuccess;
}
private static boolean decodeServiceCategoryProgramData(BearerData bData,
BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException
{
if (inStream.available() < 13) {
throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
+ inStream.available() + " bits available");
}
int paramBits = inStream.read(8) * 8;
int msgEncoding = inStream.read(5);
paramBits -= 5;
if (inStream.available() < paramBits) {
throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
+ inStream.available() + " bits available (" + paramBits + " bits expected)");
}
ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>();
final int CATEGORY_FIELD_MIN_SIZE = 6 * 8;
boolean decodeSuccess = false;
while (paramBits >= CATEGORY_FIELD_MIN_SIZE) {
int operation = inStream.read(4);
int category = (inStream.read(8) << 8) | inStream.read(8);
String language = getLanguageCodeForValue(inStream.read(8));
int maxMessages = inStream.read(8);
int alertOption = inStream.read(4);
int numFields = inStream.read(8);
paramBits -= CATEGORY_FIELD_MIN_SIZE;
int textBits = getBitsForNumFields(msgEncoding, numFields);
if (paramBits < textBits) {
throw new CodingException("category name is " + textBits + " bits in length,"
+ " but there are only " + paramBits + " bits available");
}
UserData userData = new UserData();
userData.msgEncoding = msgEncoding;
userData.msgEncodingSet = true;
userData.numFields = numFields;
userData.payload = inStream.readByteArray(textBits);
paramBits -= textBits;
decodeUserDataPayload(userData, false);
String categoryName = userData.payloadStr;
CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category,
language, maxMessages, alertOption, categoryName);
programDataList.add(programData);
decodeSuccess = true;
}
if ((! decodeSuccess) || (paramBits > 0)) {
Log.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ')');
}
inStream.skip(paramBits);
bData.serviceCategoryProgramData = programDataList;
return decodeSuccess;
}
private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
switch (serviceCategory) {
case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT:
return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT:
return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE:
return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
default:
return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
}
}
/**
* Calculates the number of bits to read for the specified number of encoded characters.
* @param msgEncoding the message encoding to use
* @param numFields the number of characters to read. For Shift-JIS and Korean encodings,
* this is the number of bytes to read.
* @return the number of bits to read from the stream
* @throws CodingException if the specified encoding is not supported
*/
private static int getBitsForNumFields(int msgEncoding, int numFields)
throws CodingException {
switch (msgEncoding) {
case UserData.ENCODING_OCTET:
case UserData.ENCODING_SHIFT_JIS:
case UserData.ENCODING_KOREAN:
case UserData.ENCODING_LATIN:
case UserData.ENCODING_LATIN_HEBREW:
return numFields * 8;
case UserData.ENCODING_IA5:
case UserData.ENCODING_7BIT_ASCII:
case UserData.ENCODING_GSM_7BIT_ALPHABET:
return numFields * 7;
case UserData.ENCODING_UNICODE_16:
return numFields * 16;
default:
throw new CodingException("unsupported message encoding (" + msgEncoding + ')');
}
}
/**
* CMAS message decoding.
* (See TIA-1149-0-1, CMAS over CDMA)
*
* @param serviceCategory is the service category from the SMS envelope
*/
private static void decodeCmasUserData(BearerData bData, int serviceCategory)
throws BitwiseInputStream.AccessException, CodingException {
BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
if (inStream.available() < 8) {
throw new CodingException("emergency CB with no CMAE_protocol_version");
}
int protocolVersion = inStream.read(8);
if (protocolVersion != 0) {
throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion);
}
int messageClass = serviceCategoryToCmasMessageClass(serviceCategory);
int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
while (inStream.available() >= 16) {
int recordType = inStream.read(8);
int recordLen = inStream.read(8);
switch (recordType) {
case 0: // Type 0 elements (Alert text)
UserData alertUserData = new UserData();
alertUserData.msgEncoding = inStream.read(5);
alertUserData.msgEncodingSet = true;
alertUserData.msgType = 0;
int numFields; // number of chars to decode
switch (alertUserData.msgEncoding) {
case UserData.ENCODING_OCTET:
case UserData.ENCODING_LATIN:
numFields = recordLen - 1; // subtract 1 byte for encoding
break;
case UserData.ENCODING_IA5:
case UserData.ENCODING_7BIT_ASCII:
case UserData.ENCODING_GSM_7BIT_ALPHABET:
numFields = ((recordLen * 8) - 5) / 7; // subtract 5 bits for encoding
break;
case UserData.ENCODING_UNICODE_16:
numFields = (recordLen - 1) / 2;
break;
default:
numFields = 0; // unsupported encoding
}
alertUserData.numFields = numFields;
alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5);
decodeUserDataPayload(alertUserData, false);
bData.userData = alertUserData;
break;
case 1: // Type 1 elements
category = inStream.read(8);
responseType = inStream.read(8);
severity = inStream.read(4);
urgency = inStream.read(4);
certainty = inStream.read(4);
inStream.skip(recordLen * 8 - 28);
break;
default:
Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
inStream.skip(recordLen * 8);
break;
}
}
bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
urgency, certainty);
}
/**
* Create BearerData object from serialized representation.
* (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
*
* @param smsData byte array of raw encoded SMS bearer data.
*
* @return an instance of BearerData.
*/
public static BearerData decode(byte[] smsData) {
return decode(smsData, 0);
}
private static boolean isCmasAlertCategory(int category) {
return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT
&& category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE;
}
/**
* Create BearerData object from serialized representation.
* (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
*
* @param smsData byte array of raw encoded SMS bearer data.
* @param serviceCategory the envelope service category (for CMAS alert handling)
* @return an instance of BearerData.
*/
public static BearerData decode(byte[] smsData, int serviceCategory) {
try {
BitwiseInputStream inStream = new BitwiseInputStream(smsData);
BearerData bData = new BearerData();
int foundSubparamMask = 0;
while (inStream.available() > 0) {
boolean decodeSuccess = false;
int subparamId = inStream.read(8);
int subparamIdBit = 1 << subparamId;
if ((foundSubparamMask & subparamIdBit) != 0) {
throw new CodingException("illegal duplicate subparameter (" +
subparamId + ")");
}
boolean decodeSuccess;
switch (subparamId) {
case SUBPARAM_MESSAGE_IDENTIFIER:
decodeSuccess = decodeMessageId(bData, inStream);
@ -1903,9 +1624,6 @@ public final class BearerData {
case SUBPARAM_MESSAGE_DEPOSIT_INDEX:
decodeSuccess = decodeDepositIndex(bData, inStream);
break;
case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA:
decodeSuccess = decodeServiceCategoryProgramData(bData, inStream);
break;
default:
throw new CodingException("unsupported bearer data subparameter ("
+ subparamId + ")");
@ -1916,10 +1634,7 @@ public final class BearerData {
throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
}
if (bData.userData != null) {
if (isCmasAlertCategory(serviceCategory) && bData.priorityIndicatorSet
&& bData.priority == SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY) {
decodeCmasUserData(bData, serviceCategory);
} else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
if ((foundSubparamMask ^
(1 << SUBPARAM_MESSAGE_IDENTIFIER) ^
(1 << SUBPARAM_USER_DATA))

View File

@ -37,7 +37,6 @@ public final class SmsEnvelope {
static public final int TELESERVICE_VMN = 0x1003;
static public final int TELESERVICE_WAP = 0x1004;
static public final int TELESERVICE_WEMT = 0x1005;
static public final int TELESERVICE_SCPT = 0x1006;
/**
* The following are defined as extensions to the standard teleservices
@ -47,17 +46,14 @@ public final class SmsEnvelope {
// number of messages waiting, it's used by some CDMA carriers for a voice mail count.
static public final int TELESERVICE_MWI = 0x40000;
// Service Categories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
// static final int SERVICE_CATEGORY_EMERGENCY = 0x0001;
// ServiceCategories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
//static public final int SERVICECATEGORY_EMERGENCY = 0x0010;
//...
// CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1
public static final int SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT = 0x1000;
public static final int SERVICE_CATEGORY_CMAS_EXTREME_THREAT = 0x1001;
public static final int SERVICE_CATEGORY_CMAS_SEVERE_THREAT = 0x1002;
public static final int SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY = 0x1003;
public static final int SERVICE_CATEGORY_CMAS_TEST_MESSAGE = 0x1004;
public static final int SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE = 0x10ff;
/**
* maximum lengths for fields as defined in ril_cdma_sms.h
*/
static public final int SMS_BEARER_DATA_MAX = 255;
/**
* Provides the type of a SMS message like point to point, broadcast or acknowledge

View File

@ -32,9 +32,9 @@ public class UserData {
public static final int ENCODING_7BIT_ASCII = 0x02;
public static final int ENCODING_IA5 = 0x03;
public static final int ENCODING_UNICODE_16 = 0x04;
public static final int ENCODING_SHIFT_JIS = 0x05;
public static final int ENCODING_KOREAN = 0x06;
public static final int ENCODING_LATIN_HEBREW = 0x07;
//public static final int ENCODING_SHIFT_JIS = 0x05;
//public static final int ENCODING_KOREAN = 0x06;
//public static final int ENCODING_LATIN_HEBREW = 0x07;
public static final int ENCODING_LATIN = 0x08;
public static final int ENCODING_GSM_7BIT_ALPHABET = 0x09;
public static final int ENCODING_GSM_DCS = 0x0A;

View File

@ -26,7 +26,6 @@ import android.os.SystemProperties;
import android.provider.Telephony.Sms;
import android.provider.Telephony.Sms.Intents;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
import android.telephony.SmsManager;
import android.telephony.gsm.GsmCellLocation;
@ -319,18 +318,24 @@ public final class GsmSMSDispatcher extends SMSDispatcher {
* concatenated message
*/
private static final class SmsCbConcatInfo {
private final SmsCbHeader mHeader;
private final SmsCbLocation mLocation;
public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
private final String mPlmn;
private final int mLac;
private final int mCid;
public SmsCbConcatInfo(SmsCbHeader header, String plmn, int lac, int cid) {
mHeader = header;
mLocation = location;
mPlmn = plmn;
mLac = lac;
mCid = cid;
}
@Override
public int hashCode() {
return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
return mHeader.messageIdentifier * 31 + mHeader.updateNumber;
}
@Override
@ -338,28 +343,49 @@ public final class GsmSMSDispatcher extends SMSDispatcher {
if (obj instanceof SmsCbConcatInfo) {
SmsCbConcatInfo other = (SmsCbConcatInfo)obj;
// Two pages match if they have the same serial number (which includes the
// geographical scope and update number), and both pages belong to the same
// location (PLMN, plus LAC and CID if these are part of the geographical scope).
return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
&& mLocation.equals(other.mLocation);
// Two pages match if all header attributes (except the page
// index) are identical, and both pages belong to the same
// location (which is also determined by the scope parameter)
if (mHeader.geographicalScope == other.mHeader.geographicalScope
&& mHeader.messageCode == other.mHeader.messageCode
&& mHeader.updateNumber == other.mHeader.updateNumber
&& mHeader.messageIdentifier == other.mHeader.messageIdentifier
&& mHeader.dataCodingScheme == other.mHeader.dataCodingScheme
&& mHeader.nrOfPages == other.mHeader.nrOfPages) {
return matchesLocation(other.mPlmn, other.mLac, other.mCid);
}
}
return false;
}
/**
* Compare the location code for this message to the current location code. The match is
* relative to the geographical scope of the message, which determines whether the LAC
* and Cell ID are saved in mLocation or set to -1 to match all values.
* Checks if this concatenation info matches the given location. The
* granularity of the match depends on the geographical scope.
*
* @param plmn the current PLMN
* @param lac the current Location Area (GSM) or Service Area (UMTS)
* @param cid the current Cell ID
* @return true if this message is valid for the current location; false otherwise
* @param plmn PLMN
* @param lac Location area code
* @param cid Cell ID
* @return true if matching, false otherwise
*/
public boolean matchesLocation(String plmn, int lac, int cid) {
return mLocation.isInLocationArea(plmn, lac, cid);
switch (mHeader.geographicalScope) {
case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
if (mCid != cid) {
return false;
}
// deliberate fall-through
case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE:
if (mLac != lac) {
return false;
}
// deliberate fall-through
case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
return mPlmn != null && mPlmn.equals(plmn);
}
return false;
}
}
@ -395,42 +421,24 @@ public final class GsmSMSDispatcher extends SMSDispatcher {
int lac = cellLocation.getLac();
int cid = cellLocation.getCid();
SmsCbLocation location;
switch (header.getGeographicalScope()) {
case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE:
location = new SmsCbLocation(plmn, lac, -1);
break;
case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
location = new SmsCbLocation(plmn, lac, cid);
break;
case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
default:
location = new SmsCbLocation(plmn);
break;
}
byte[][] pdus;
int pageCount = header.getNumberOfPages();
if (pageCount > 1) {
if (header.nrOfPages > 1) {
// Multi-page message
SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, plmn, lac, cid);
// Try to find other pages of the same message
pdus = mSmsCbPageMap.get(concatInfo);
if (pdus == null) {
// This is the first page of this message, make room for all
// This it the first page of this message, make room for all
// pages and keep until complete
pdus = new byte[pageCount][];
pdus = new byte[header.nrOfPages][];
mSmsCbPageMap.put(concatInfo, pdus);
}
// Page parameter is one-based
pdus[header.getPageIndex() - 1] = receivedPdu;
pdus[header.pageIndex - 1] = receivedPdu;
for (int i = 0; i < pdus.length; i++) {
if (pdus[i] == null) {
@ -447,8 +455,8 @@ public final class GsmSMSDispatcher extends SMSDispatcher {
pdus[0] = receivedPdu;
}
SmsCbMessage message = GsmSmsCbMessage.createSmsCbMessage(header, location, pdus);
dispatchBroadcastMessage(message);
boolean isEmergencyMessage = SmsCbHeader.isEmergencyMessage(header.messageIdentifier);
dispatchBroadcastPdus(pdus, isEmergencyMessage);
// Remove messages that are out of scope to prevent the map from
// growing indefinitely, containing incomplete messages that were

View File

@ -1,286 +0,0 @@
/*
* Copyright (C) 2012 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.SmsCbLocation;
import android.telephony.SmsCbMessage;
import android.util.Pair;
import com.android.internal.telephony.GsmAlphabet;
import java.io.UnsupportedEncodingException;
/**
* Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
* public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
*/
public class GsmSmsCbMessage {
/**
* Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
*/
private static final String[] LANGUAGE_CODES_GROUP_0 = {
"de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu",
"pl", null
};
/**
* Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
*/
private static final String[] LANGUAGE_CODES_GROUP_2 = {
"cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null,
null, null
};
private static final char CARRIAGE_RETURN = 0x0d;
private static final int PDU_BODY_PAGE_LENGTH = 82;
/** Utility class with only static methods. */
private GsmSmsCbMessage() { }
/**
* Create a new SmsCbMessage object from a header object plus one or more received PDUs.
*
* @param pdus PDU bytes
*/
static SmsCbMessage createSmsCbMessage(SmsCbHeader header, SmsCbLocation location,
byte[][] pdus) throws IllegalArgumentException {
if (header.isEtwsPrimaryNotification()) {
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
header.getGeographicalScope(), header.getSerialNumber(),
location, header.getServiceCategory(),
null, "ETWS", SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
header.getEtwsInfo(), header.getCmasInfo());
} else {
String language = null;
StringBuilder sb = new StringBuilder();
for (byte[] pdu : pdus) {
Pair<String, String> p = parseBody(header, pdu);
language = p.first;
sb.append(p.second);
}
int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
: SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
header.getGeographicalScope(), header.getSerialNumber(), location,
header.getServiceCategory(), language, sb.toString(), priority,
header.getEtwsInfo(), header.getCmasInfo());
}
}
/**
* Create a new SmsCbMessage object from one or more received PDUs. This is used by some
* CellBroadcastReceiver test cases, because SmsCbHeader is now package local.
*
* @param location the location (geographical scope) for the message
* @param pdus PDU bytes
*/
public static SmsCbMessage createSmsCbMessage(SmsCbLocation location, byte[][] pdus)
throws IllegalArgumentException {
SmsCbHeader header = new SmsCbHeader(pdus[0]);
return createSmsCbMessage(header, location, pdus);
}
/**
* Parse and unpack the body text according to the encoding in the DCS.
* After completing successfully this method will have assigned the body
* text into mBody, and optionally the language code into mLanguage
*
* @param header the message header to use
* @param pdu the PDU to decode
* @return a Pair of Strings containing the language and body of the message
*/
private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) {
int encoding;
String language = null;
boolean hasLanguageIndicator = false;
int dataCodingScheme = header.getDataCodingScheme();
// Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
// section 5.
switch ((dataCodingScheme & 0xf0) >> 4) {
case 0x00:
encoding = android.telephony.SmsMessage.ENCODING_7BIT;
language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
break;
case 0x01:
hasLanguageIndicator = true;
if ((dataCodingScheme & 0x0f) == 0x01) {
encoding = android.telephony.SmsMessage.ENCODING_16BIT;
} else {
encoding = android.telephony.SmsMessage.ENCODING_7BIT;
}
break;
case 0x02:
encoding = android.telephony.SmsMessage.ENCODING_7BIT;
language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
break;
case 0x03:
encoding = android.telephony.SmsMessage.ENCODING_7BIT;
break;
case 0x04:
case 0x05:
switch ((dataCodingScheme & 0x0c) >> 2) {
case 0x01:
encoding = android.telephony.SmsMessage.ENCODING_8BIT;
break;
case 0x02:
encoding = android.telephony.SmsMessage.ENCODING_16BIT;
break;
case 0x00:
default:
encoding = android.telephony.SmsMessage.ENCODING_7BIT;
break;
}
break;
case 0x06:
case 0x07:
// Compression not supported
case 0x09:
// UDH structure not supported
case 0x0e:
// Defined by the WAP forum not supported
throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
+ dataCodingScheme);
case 0x0f:
if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
encoding = android.telephony.SmsMessage.ENCODING_8BIT;
} else {
encoding = android.telephony.SmsMessage.ENCODING_7BIT;
}
break;
default:
// Reserved values are to be treated as 7-bit
encoding = android.telephony.SmsMessage.ENCODING_7BIT;
break;
}
if (header.isUmtsFormat()) {
// Payload may contain multiple pages
int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
* nrPages) {
throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
+ nrPages + " pages");
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nrPages; i++) {
// Each page is 82 bytes followed by a length octet indicating
// the number of useful octets within those 82
int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
if (length > PDU_BODY_PAGE_LENGTH) {
throw new IllegalArgumentException("Page length " + length
+ " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
}
Pair<String, String> p = unpackBody(pdu, encoding, offset, length,
hasLanguageIndicator, language);
language = p.first;
sb.append(p.second);
}
return new Pair<String, String>(language, sb.toString());
} else {
// Payload is one single page
int offset = SmsCbHeader.PDU_HEADER_LENGTH;
int length = pdu.length - offset;
return unpackBody(pdu, encoding, offset, length, hasLanguageIndicator, language);
}
}
/**
* Unpack body text from the pdu using the given encoding, position and
* length within the pdu
*
* @param pdu The pdu
* @param encoding The encoding, as derived from the DCS
* @param offset Position of the first byte to unpack
* @param length Number of bytes to unpack
* @param hasLanguageIndicator true if the body text is preceded by a
* language indicator. If so, this method will as a side-effect
* assign the extracted language code into mLanguage
* @param language the language to return if hasLanguageIndicator is false
* @return a Pair of Strings containing the language and body of the message
*/
private static Pair<String, String> unpackBody(byte[] pdu, int encoding, int offset, int length,
boolean hasLanguageIndicator, String language) {
String body = null;
switch (encoding) {
case android.telephony.SmsMessage.ENCODING_7BIT:
body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
if (hasLanguageIndicator && body != null && body.length() > 2) {
// Language is two GSM characters followed by a CR.
// The actual body text is offset by 3 characters.
language = body.substring(0, 2);
body = body.substring(3);
}
break;
case android.telephony.SmsMessage.ENCODING_16BIT:
if (hasLanguageIndicator && pdu.length >= offset + 2) {
// Language is two GSM characters.
// The actual body text is offset by 2 bytes.
language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
offset += 2;
length -= 2;
}
try {
body = new String(pdu, offset, (length & 0xfffe), "utf-16");
} catch (UnsupportedEncodingException e) {
// Apparently it wasn't valid UTF-16.
throw new IllegalArgumentException("Error decoding UTF-16 message", e);
}
break;
default:
break;
}
if (body != null) {
// Remove trailing carriage return
for (int i = body.length() - 1; i >= 0; i--) {
if (body.charAt(i) != CARRIAGE_RETURN) {
body = body.substring(0, i + 1);
break;
}
}
} else {
body = "";
}
return new Pair<String, String>(language, body);
}
}

View File

@ -1,125 +0,0 @@
/*
* Copyright (C) 2012 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;
/**
* Constants used in SMS Cell Broadcast messages (see 3GPP TS 23.041). This class is used by the
* boot-time broadcast channel enable and database upgrade code in CellBroadcastReceiver, so it
* is public, but should be avoided in favor of the radio technology independent constants in
* {@link android.telephony.SmsCbMessage}, {@link android.telephony.SmsCbEtwsInfo}, and
* {@link android.telephony.SmsCbCmasInfo} classes.
*
* {@hide}
*/
public class SmsCbConstants {
/** Private constructor for utility class. */
private SmsCbConstants() { }
/** Start of PWS Message Identifier range (includes ETWS and CMAS). */
public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER = 0x1100;
/** Bitmask for messages of ETWS type (including future extensions). */
public static final int MESSAGE_ID_ETWS_TYPE_MASK = 0xFFF8;
/** Value for messages of ETWS type after applying {@link #MESSAGE_ID_ETWS_TYPE_MASK}. */
public static final int MESSAGE_ID_ETWS_TYPE = 0x1100;
/** ETWS Message Identifier for earthquake warning message. */
public static final int MESSAGE_ID_ETWS_EARTHQUAKE_WARNING = 0x1100;
/** ETWS Message Identifier for tsunami warning message. */
public static final int MESSAGE_ID_ETWS_TSUNAMI_WARNING = 0x1101;
/** ETWS Message Identifier for earthquake and tsunami combined warning message. */
public static final int MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING = 0x1102;
/** ETWS Message Identifier for test message. */
public static final int MESSAGE_ID_ETWS_TEST_MESSAGE = 0x1103;
/** ETWS Message Identifier for messages related to other emergency types. */
public static final int MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE = 0x1104;
/** Start of CMAS Message Identifier range. */
public static final int MESSAGE_ID_CMAS_FIRST_IDENTIFIER = 0x1112;
/** CMAS Message Identifier for Presidential Level alerts. */
public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL = 0x1112;
/** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED = 0x1113;
/** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY = 0x1114;
/** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED = 0x1115;
/** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY = 0x1116;
/** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed. */
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED = 0x1117;
/** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely. */
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY = 0x1118;
/** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed. */
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED = 0x1119;
/** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely. */
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY = 0x111A;
/** CMAS Message Identifier for Child Abduction Emergency (Amber Alert). */
public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY = 0x111B;
/** CMAS Message Identifier for the Required Monthly Test. */
public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST = 0x111C;
/** CMAS Message Identifier for CMAS Exercise. */
public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE = 0x111D;
/** CMAS Message Identifier for operator defined use. */
public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE = 0x111E;
/** End of CMAS Message Identifier range (including future extensions). */
public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER = 0x112F;
/** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER = 0x18FF;
/** ETWS serial number flag to activate the popup display. */
public static final int SERIAL_NUMBER_ETWS_ACTIVATE_POPUP = 0x1000;
/** ETWS serial number flag to activate the emergency user alert. */
public static final int SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT = 0x2000;
/** ETWS warning type value for earthquake. */
public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
/** ETWS warning type value for tsunami. */
public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01;
/** ETWS warning type value for earthquake and tsunami. */
public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02;
/** ETWS warning type value for test broadcast. */
public static final int ETWS_WARNING_TYPE_TEST = 0x03;
/** ETWS warning type value for other notifications. */
public static final int ETWS_WARNING_TYPE_OTHER = 0x04;
}

View File

@ -16,42 +16,28 @@
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 {
import android.telephony.SmsCbConstants;
public class SmsCbHeader implements SmsCbConstants {
/**
* Length of SMS-CB header
*/
static final int PDU_HEADER_LENGTH = 6;
public 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;
public 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;
public 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;
public static final int FORMAT_ETWS_PRIMARY = 3;
/**
* Message type value as defined in 3gpp TS 25.324, section 11.1.
@ -61,34 +47,34 @@ class SmsCbHeader {
/**
* Length of GSM pdus
*/
private static final int PDU_LENGTH_GSM = 88;
public static final int PDU_LENGTH_GSM = 88;
/**
* Maximum length of ETWS primary message GSM pdus
*/
private static final int PDU_LENGTH_ETWS = 56;
public static final int PDU_LENGTH_ETWS = 56;
private final int geographicalScope;
public final int geographicalScope;
/** The serial number combines geographical scope, message code, and update number. */
private final int serialNumber;
public final int messageCode;
/** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */
private final int messageIdentifier;
public final int updateNumber;
private final int dataCodingScheme;
public final int messageIdentifier;
private final int pageIndex;
public final int dataCodingScheme;
private final int nrOfPages;
public final int pageIndex;
private final int format;
public final int nrOfPages;
/** ETWS warning notification info. */
private final SmsCbEtwsInfo mEtwsInfo;
public final int format;
/** CMAS warning notification info. */
private final SmsCbCmasInfo mCmasInfo;
public final boolean etwsEmergencyUserAlert;
public final boolean etwsPopup;
public final int etwsWarningType;
public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
@ -97,31 +83,22 @@ class SmsCbHeader {
if (pdu.length <= PDU_LENGTH_ETWS) {
format = FORMAT_ETWS_PRIMARY;
geographicalScope = (pdu[0] & 0xc0) >> 6;
serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
geographicalScope = -1; //not applicable
messageCode = -1;
updateNumber = -1;
messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
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
etwsEmergencyUserAlert = (pdu[4] & 0x1) != 0;
etwsPopup = (pdu[5] & 0x80) != 0;
etwsWarningType = (pdu[4] & 0xfe) >> 1;
} else if (pdu.length <= PDU_LENGTH_GSM) {
// GSM pdus are no more than 88 bytes
format = FORMAT_GSM;
geographicalScope = (pdu[0] & 0xc0) >> 6;
serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
messageCode = ((pdu[0] & 0x3f) << 4) | ((pdu[1] & 0xf0) >> 4);
updateNumber = pdu[1] & 0x0f;
messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
dataCodingScheme = pdu[4] & 0xff;
@ -136,6 +113,9 @@ class SmsCbHeader {
this.pageIndex = pageIndex;
this.nrOfPages = nrOfPages;
etwsEmergencyUserAlert = false;
etwsPopup = false;
etwsWarningType = -1;
} 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
@ -149,7 +129,8 @@ class SmsCbHeader {
messageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
geographicalScope = (pdu[3] & 0xc0) >> 6;
serialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
messageCode = ((pdu[3] & 0x3f) << 4) | ((pdu[4] & 0xf0) >> 4);
updateNumber = pdu[4] & 0x0f;
dataCodingScheme = pdu[5] & 0xff;
// We will always consider a UMTS message as having one single page
@ -157,251 +138,75 @@ class SmsCbHeader {
// 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;
etwsEmergencyUserAlert = false;
etwsPopup = false;
etwsWarningType = -1;
}
}
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
* Return whether the specified message ID is an emergency (PWS) message type.
* This method is static and takes an argument so that it can be used by
* CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
* @param id the message identifier to check
* @return true if the message is emergency type; false otherwise
*/
boolean isEmergencyMessage() {
return messageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
&& messageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
public static boolean isEmergencyMessage(int id) {
return id >= MESSAGE_ID_PWS_FIRST_IDENTIFIER && id <= 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
* Return whether the specified message ID is an ETWS emergency message type.
* This method is static and takes an argument so that it can be used by
* CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
* @param id the message identifier to check
* @return true if the message is ETWS emergency type; false otherwise
*/
private boolean isEtwsMessage() {
return (messageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
== SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
public static boolean isEtwsMessage(int id) {
return (id & MESSAGE_ID_ETWS_TYPE_MASK) == 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
* Return whether the specified message ID is a CMAS emergency message type.
* This method is static and takes an argument so that it can be used by
* CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
* @param id the message identifier to check
* @return true if the message is CMAS emergency type; false otherwise
*/
boolean isEtwsPrimaryNotification() {
return format == FORMAT_ETWS_PRIMARY;
public static boolean isCmasMessage(int id) {
return id >= MESSAGE_ID_CMAS_FIRST_IDENTIFIER && id <= MESSAGE_ID_CMAS_LAST_IDENTIFIER;
}
/**
* 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.
* Return whether the specified message code indicates an ETWS popup alert.
* This method is static and takes an argument so that it can be used by
* CellBroadcastReceiver, which stores message codes in SQLite rather than PDU.
* This method assumes that the message ID has already been checked for ETWS type.
*
* @param messageCode the message code to check
* @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;
public static boolean isEtwsPopupAlert(int messageCode) {
return (messageCode & MESSAGE_CODE_ETWS_ACTIVATE_POPUP) != 0;
}
/**
* Return whether the emergency user alert flag is set for an ETWS warning notification.
* Return whether the specified message code indicates an ETWS emergency user alert.
* This method is static and takes an argument so that it can be used by
* CellBroadcastReceiver, which stores message codes in SQLite rather than PDU.
* This method assumes that the message ID has already been checked for ETWS type.
*
* @param messageCode the message code to check
* @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;
}
public static boolean isEtwsEmergencyUserAlert(int messageCode) {
return (messageCode & MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT) != 0;
}
@Override
public String toString() {
return "SmsCbHeader{GS=" + geographicalScope + ", serialNumber=0x" +
Integer.toHexString(serialNumber) +
return "SmsCbHeader{GS=" + geographicalScope + ", messageCode=0x" +
Integer.toHexString(messageCode) + ", updateNumber=" + updateNumber +
", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) +
", DCS=0x" + Integer.toHexString(dataCodingScheme) +
", page " + pageIndex + " of " + nrOfPages + '}';