Merge "Support WAC decoding in UMTS format"
This commit is contained in:
@@ -4021,8 +4021,8 @@ public final class Telephony {
|
||||
public static final String DEFAULT_SORT_ORDER = DELIVERY_TIME + " DESC";
|
||||
|
||||
/**
|
||||
* The Epoch Unix timestamp when the device received the message.
|
||||
* <P>Type: INTEGER</P>
|
||||
* The timestamp in millisecond of when the device received the message.
|
||||
* <P>Type: BIGINT</P>
|
||||
*/
|
||||
public static final String RECEIVED_TIME = "received_time";
|
||||
|
||||
@@ -4092,6 +4092,33 @@ public final class Telephony {
|
||||
CMAS_URGENCY,
|
||||
CMAS_CERTAINTY
|
||||
};
|
||||
|
||||
/**
|
||||
* Query columns for instantiating {@link android.telephony.SmsCbMessage} objects.
|
||||
*/
|
||||
public static final String[] QUERY_COLUMNS_FWK = {
|
||||
_ID,
|
||||
GEOGRAPHICAL_SCOPE,
|
||||
PLMN,
|
||||
LAC,
|
||||
CID,
|
||||
SERIAL_NUMBER,
|
||||
SERVICE_CATEGORY,
|
||||
LANGUAGE_CODE,
|
||||
MESSAGE_BODY,
|
||||
MESSAGE_FORMAT,
|
||||
MESSAGE_PRIORITY,
|
||||
ETWS_WARNING_TYPE,
|
||||
CMAS_MESSAGE_CLASS,
|
||||
CMAS_CATEGORY,
|
||||
CMAS_RESPONSE_TYPE,
|
||||
CMAS_SEVERITY,
|
||||
CMAS_URGENCY,
|
||||
CMAS_CERTAINTY,
|
||||
RECEIVED_TIME,
|
||||
MESSAGE_BROADCASTED,
|
||||
GEOMETRIES
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,6 +53,11 @@ public class CbGeoUtils {
|
||||
|
||||
private static final String TAG = "CbGeoUtils";
|
||||
|
||||
/** The TLV tags of WAC, defined in ATIS-0700041 5.2.3 WAC tag coding. */
|
||||
public static final int GEO_FENCING_MAXIMUM_WAIT_TIME = 0x01;
|
||||
public static final int GEOMETRY_TYPE_POLYGON = 0x02;
|
||||
public static final int GEOMETRY_TYPE_CIRCLE = 0x03;
|
||||
|
||||
/** The identifier of geometry in the encoded string. */
|
||||
private static final String CIRCLE_SYMBOL = "circle";
|
||||
private static final String POLYGON_SYMBOL = "polygon";
|
||||
@@ -92,6 +97,11 @@ public class CbGeoUtils {
|
||||
+ dlng * dlng * Math.cos(Math.toRadians(lat)) * Math.cos(Math.toRadians(p.lat));
|
||||
return 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1 - x)) * EARTH_RADIUS_METER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + lat + "," + lng + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,8 +16,17 @@
|
||||
|
||||
package android.telephony;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.Telephony.CellBroadcasts;
|
||||
|
||||
import com.android.internal.telephony.CbGeoUtils;
|
||||
import com.android.internal.telephony.CbGeoUtils.Geometry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Parcelable object containing a received cell broadcast message. There are four different types
|
||||
@@ -138,12 +147,31 @@ public class SmsCbMessage implements Parcelable {
|
||||
/** CMAS warning notification information (CMAS warnings only). */
|
||||
private final SmsCbCmasInfo mCmasWarningInfo;
|
||||
|
||||
/** UNIX timestamp of when the message was received. */
|
||||
private final long mReceivedTimeMillis;
|
||||
|
||||
/** CMAS warning area coordinates. */
|
||||
private final List<Geometry> mGeometries;
|
||||
|
||||
/**
|
||||
* Create a new SmsCbMessage with the specified data.
|
||||
*/
|
||||
public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
|
||||
SmsCbLocation location, int serviceCategory, String language, String body,
|
||||
int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) {
|
||||
|
||||
this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language,
|
||||
body, priority, etwsWarningInfo, cmasWarningInfo, null /* geometries */,
|
||||
System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SmsCbMessage} with the warning area coordinates information.
|
||||
*/
|
||||
public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
|
||||
SmsCbLocation location, int serviceCategory, String language, String body,
|
||||
int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo,
|
||||
List<Geometry> geometries, long receivedTimeMillis) {
|
||||
mMessageFormat = messageFormat;
|
||||
mGeographicalScope = geographicalScope;
|
||||
mSerialNumber = serialNumber;
|
||||
@@ -154,6 +182,8 @@ public class SmsCbMessage implements Parcelable {
|
||||
mPriority = priority;
|
||||
mEtwsWarningInfo = etwsWarningInfo;
|
||||
mCmasWarningInfo = cmasWarningInfo;
|
||||
mReceivedTimeMillis = receivedTimeMillis;
|
||||
mGeometries = geometries;
|
||||
}
|
||||
|
||||
/** Create a new SmsCbMessage object from a Parcel. */
|
||||
@@ -184,6 +214,9 @@ public class SmsCbMessage implements Parcelable {
|
||||
mEtwsWarningInfo = null;
|
||||
mCmasWarningInfo = null;
|
||||
}
|
||||
mReceivedTimeMillis = in.readLong();
|
||||
String geoStr = in.readString();
|
||||
mGeometries = geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,6 +247,9 @@ public class SmsCbMessage implements Parcelable {
|
||||
// no ETWS or CMAS warning information
|
||||
dest.writeInt('0');
|
||||
}
|
||||
dest.writeLong(mReceivedTimeMillis);
|
||||
dest.writeString(
|
||||
mGeometries != null ? CbGeoUtils.encodeGeometriesToString(mGeometries) : null);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SmsCbMessage> CREATOR
|
||||
@@ -292,6 +328,24 @@ public class SmsCbMessage implements Parcelable {
|
||||
return mBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the warning area coordinates information represent by polygons and circles.
|
||||
* @return a list of geometries, {@link Nullable} means there is no coordinate information
|
||||
* associated to this message.
|
||||
*/
|
||||
@Nullable
|
||||
public List<Geometry> getGeometries() {
|
||||
return mGeometries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time when this message was received.
|
||||
* @return the time in millisecond
|
||||
*/
|
||||
public long getReceivedTime() {
|
||||
return mReceivedTimeMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
|
||||
* @return an integer representing 3GPP or 3GPP2 message format
|
||||
@@ -368,7 +422,10 @@ public class SmsCbMessage implements Parcelable {
|
||||
+ mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
|
||||
+ ", priority=" + mPriority
|
||||
+ (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
|
||||
+ (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}';
|
||||
+ (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "")
|
||||
+ ", geo=" + (mGeometries != null
|
||||
? CbGeoUtils.encodeGeometriesToString(mGeometries) : "null")
|
||||
+ '}';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -379,4 +436,171 @@ public class SmsCbMessage implements Parcelable {
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ContentValues} instance that includes the cell broadcast data.
|
||||
*/
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues cv = new ContentValues(16);
|
||||
cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope);
|
||||
if (mLocation.getPlmn() != null) {
|
||||
cv.put(CellBroadcasts.PLMN, mLocation.getPlmn());
|
||||
}
|
||||
if (mLocation.getLac() != -1) {
|
||||
cv.put(CellBroadcasts.LAC, mLocation.getLac());
|
||||
}
|
||||
if (mLocation.getCid() != -1) {
|
||||
cv.put(CellBroadcasts.CID, mLocation.getCid());
|
||||
}
|
||||
cv.put(CellBroadcasts.SERIAL_NUMBER, getSerialNumber());
|
||||
cv.put(CellBroadcasts.SERVICE_CATEGORY, getServiceCategory());
|
||||
cv.put(CellBroadcasts.LANGUAGE_CODE, getLanguageCode());
|
||||
cv.put(CellBroadcasts.MESSAGE_BODY, getMessageBody());
|
||||
cv.put(CellBroadcasts.MESSAGE_FORMAT, getMessageFormat());
|
||||
cv.put(CellBroadcasts.MESSAGE_PRIORITY, getMessagePriority());
|
||||
|
||||
SmsCbEtwsInfo etwsInfo = getEtwsWarningInfo();
|
||||
if (etwsInfo != null) {
|
||||
cv.put(CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
|
||||
}
|
||||
|
||||
SmsCbCmasInfo cmasInfo = getCmasWarningInfo();
|
||||
if (cmasInfo != null) {
|
||||
cv.put(CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass());
|
||||
cv.put(CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory());
|
||||
cv.put(CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType());
|
||||
cv.put(CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity());
|
||||
cv.put(CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency());
|
||||
cv.put(CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty());
|
||||
}
|
||||
|
||||
cv.put(CellBroadcasts.RECEIVED_TIME, mReceivedTimeMillis);
|
||||
|
||||
if (mGeometries != null) {
|
||||
cv.put(CellBroadcasts.GEOMETRIES, CbGeoUtils.encodeGeometriesToString(mGeometries));
|
||||
} else {
|
||||
cv.put(CellBroadcasts.GEOMETRIES, (String) null);
|
||||
}
|
||||
|
||||
return cv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SmsCbMessage} instance from a row in the cell broadcast database.
|
||||
* @param cursor an open SQLite cursor pointing to the row to read
|
||||
* @return a {@link SmsCbMessage} instance.
|
||||
* @throws IllegalArgumentException if one of the required columns is missing
|
||||
*/
|
||||
public static SmsCbMessage createFromCursor(Cursor cursor) {
|
||||
int geoScope = cursor.getInt(
|
||||
cursor.getColumnIndexOrThrow(CellBroadcasts.GEOGRAPHICAL_SCOPE));
|
||||
int serialNum = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERIAL_NUMBER));
|
||||
int category = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERVICE_CATEGORY));
|
||||
String language = cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(CellBroadcasts.LANGUAGE_CODE));
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_BODY));
|
||||
int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT));
|
||||
int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY));
|
||||
|
||||
String plmn;
|
||||
int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN);
|
||||
if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) {
|
||||
plmn = cursor.getString(plmnColumn);
|
||||
} else {
|
||||
plmn = null;
|
||||
}
|
||||
|
||||
int lac;
|
||||
int lacColumn = cursor.getColumnIndex(CellBroadcasts.LAC);
|
||||
if (lacColumn != -1 && !cursor.isNull(lacColumn)) {
|
||||
lac = cursor.getInt(lacColumn);
|
||||
} else {
|
||||
lac = -1;
|
||||
}
|
||||
|
||||
int cid;
|
||||
int cidColumn = cursor.getColumnIndex(CellBroadcasts.CID);
|
||||
if (cidColumn != -1 && !cursor.isNull(cidColumn)) {
|
||||
cid = cursor.getInt(cidColumn);
|
||||
} else {
|
||||
cid = -1;
|
||||
}
|
||||
|
||||
SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
|
||||
|
||||
SmsCbEtwsInfo etwsInfo;
|
||||
int etwsWarningTypeColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_WARNING_TYPE);
|
||||
if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) {
|
||||
int warningType = cursor.getInt(etwsWarningTypeColumn);
|
||||
etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null);
|
||||
} else {
|
||||
etwsInfo = null;
|
||||
}
|
||||
|
||||
SmsCbCmasInfo cmasInfo = null;
|
||||
int cmasMessageClassColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_MESSAGE_CLASS);
|
||||
if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) {
|
||||
int messageClass = cursor.getInt(cmasMessageClassColumn);
|
||||
|
||||
int cmasCategory;
|
||||
int cmasCategoryColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CATEGORY);
|
||||
if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) {
|
||||
cmasCategory = cursor.getInt(cmasCategoryColumn);
|
||||
} else {
|
||||
cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
|
||||
}
|
||||
|
||||
int responseType;
|
||||
int cmasResponseTypeColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_RESPONSE_TYPE);
|
||||
if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) {
|
||||
responseType = cursor.getInt(cmasResponseTypeColumn);
|
||||
} else {
|
||||
responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
int severity;
|
||||
int cmasSeverityColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_SEVERITY);
|
||||
if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) {
|
||||
severity = cursor.getInt(cmasSeverityColumn);
|
||||
} else {
|
||||
severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
|
||||
}
|
||||
|
||||
int urgency;
|
||||
int cmasUrgencyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_URGENCY);
|
||||
if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) {
|
||||
urgency = cursor.getInt(cmasUrgencyColumn);
|
||||
} else {
|
||||
urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
|
||||
}
|
||||
|
||||
int certainty;
|
||||
int cmasCertaintyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CERTAINTY);
|
||||
if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) {
|
||||
certainty = cursor.getInt(cmasCertaintyColumn);
|
||||
} else {
|
||||
certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
|
||||
}
|
||||
|
||||
cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity,
|
||||
urgency, certainty);
|
||||
}
|
||||
|
||||
String geoStr = cursor.getString(cursor.getColumnIndex(CellBroadcasts.GEOMETRIES));
|
||||
List<Geometry> geometries =
|
||||
geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
|
||||
|
||||
long receivedTimeSec = cursor.getLong(
|
||||
cursor.getColumnIndexOrThrow(CellBroadcasts.RECEIVED_TIME));
|
||||
|
||||
return new SmsCbMessage(format, geoScope, serialNum, location, category,
|
||||
language, body, priority, etwsInfo, cmasInfo, geometries, receivedTimeSec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code True} if this message needs geo-fencing check.
|
||||
*/
|
||||
public boolean needGeoFencingCheck() {
|
||||
return mGeometries != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,58 +22,36 @@ import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
|
||||
import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
|
||||
import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.telephony.SmsCbLocation;
|
||||
import android.telephony.SmsCbMessage;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.telephony.CbGeoUtils;
|
||||
import com.android.internal.telephony.CbGeoUtils.Circle;
|
||||
import com.android.internal.telephony.CbGeoUtils.Geometry;
|
||||
import com.android.internal.telephony.CbGeoUtils.LatLng;
|
||||
import com.android.internal.telephony.CbGeoUtils.Polygon;
|
||||
import com.android.internal.telephony.GsmAlphabet;
|
||||
import com.android.internal.telephony.SmsConstants;
|
||||
import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
|
||||
import com.android.internal.telephony.gsm.SmsCbHeader.DataCodingScheme;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Locale;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
Locale.GERMAN.getLanguage(), // German
|
||||
Locale.ENGLISH.getLanguage(), // English
|
||||
Locale.ITALIAN.getLanguage(), // Italian
|
||||
Locale.FRENCH.getLanguage(), // French
|
||||
new Locale("es").getLanguage(), // Spanish
|
||||
new Locale("nl").getLanguage(), // Dutch
|
||||
new Locale("sv").getLanguage(), // Swedish
|
||||
new Locale("da").getLanguage(), // Danish
|
||||
new Locale("pt").getLanguage(), // Portuguese
|
||||
new Locale("fi").getLanguage(), // Finnish
|
||||
new Locale("nb").getLanguage(), // Norwegian
|
||||
new Locale("el").getLanguage(), // Greek
|
||||
new Locale("tr").getLanguage(), // Turkish
|
||||
new Locale("hu").getLanguage(), // Hungarian
|
||||
new Locale("pl").getLanguage(), // Polish
|
||||
null
|
||||
};
|
||||
|
||||
/**
|
||||
* Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
|
||||
*/
|
||||
private static final String[] LANGUAGE_CODES_GROUP_2 = {
|
||||
new Locale("cs").getLanguage(), // Czech
|
||||
new Locale("he").getLanguage(), // Hebrew
|
||||
new Locale("ar").getLanguage(), // Arabic
|
||||
new Locale("ru").getLanguage(), // Russian
|
||||
new Locale("is").getLanguage(), // Icelandic
|
||||
null, null, null, null, null, null, null, null, null, null, null
|
||||
};
|
||||
private static final String TAG = GsmSmsCbMessage.class.getSimpleName();
|
||||
|
||||
private static final char CARRIAGE_RETURN = 0x0d;
|
||||
|
||||
@@ -114,8 +92,9 @@ public class GsmSmsCbMessage {
|
||||
* @param pdus PDU bytes
|
||||
*/
|
||||
public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
|
||||
SmsCbLocation location, byte[][] pdus)
|
||||
SmsCbLocation location, byte[][] pdus)
|
||||
throws IllegalArgumentException {
|
||||
long receivedTimeMillis = System.currentTimeMillis();
|
||||
if (header.isEtwsPrimaryNotification()) {
|
||||
// ETSI TS 23.041 ETWS Primary Notification message
|
||||
// ETWS primary message only contains 4 fields including serial number,
|
||||
@@ -125,12 +104,41 @@ public class GsmSmsCbMessage {
|
||||
header.getSerialNumber(), location, header.getServiceCategory(), null,
|
||||
getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
|
||||
SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
|
||||
header.getCmasInfo());
|
||||
header.getCmasInfo(), null /* geometries */, receivedTimeMillis);
|
||||
} else if (header.isUmtsFormat()) {
|
||||
// UMTS format has only 1 PDU
|
||||
byte[] pdu = pdus[0];
|
||||
Pair<String, String> cbData = parseUmtsBody(header, pdu);
|
||||
String language = cbData.first;
|
||||
String body = cbData.second;
|
||||
int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
|
||||
: SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
|
||||
int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
|
||||
int wacDataOffset = SmsCbHeader.PDU_HEADER_LENGTH
|
||||
+ 1 // number of pages
|
||||
+ (PDU_BODY_PAGE_LENGTH + 1) * nrPages; // cb data
|
||||
|
||||
// Has Warning Area Coordinates information
|
||||
List<Geometry> geometries = null;
|
||||
if (pdu.length > wacDataOffset) {
|
||||
try {
|
||||
geometries = parseWarningAreaCoordinates(pdu, wacDataOffset);
|
||||
} catch (Exception ex) {
|
||||
// Catch the exception here, the message will be considered as having no WAC
|
||||
// information which means the message will be broadcasted directly.
|
||||
Slog.e(TAG, "Can't parse warning area coordinates, ex = " + ex.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
|
||||
header.getGeographicalScope(), header.getSerialNumber(), location,
|
||||
header.getServiceCategory(), language, body, priority,
|
||||
header.getEtwsInfo(), header.getCmasInfo(), geometries, receivedTimeMillis);
|
||||
} else {
|
||||
String language = null;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte[] pdu : pdus) {
|
||||
Pair<String, String> p = parseBody(header, pdu);
|
||||
Pair<String, String> p = parseGsmBody(header, pdu);
|
||||
language = p.first;
|
||||
sb.append(p.second);
|
||||
}
|
||||
@@ -140,154 +148,197 @@ public class GsmSmsCbMessage {
|
||||
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
|
||||
header.getGeographicalScope(), header.getSerialNumber(), location,
|
||||
header.getServiceCategory(), language, sb.toString(), priority,
|
||||
header.getEtwsInfo(), header.getCmasInfo());
|
||||
header.getEtwsInfo(), header.getCmasInfo(), null /* geometries */,
|
||||
receivedTimeMillis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Parse WEA Handset Action Message(WHAM) a.k.a geo-fencing trigger message.
|
||||
*
|
||||
* WEA Handset Action Message(WHAM) is a cell broadcast service message broadcast by the network
|
||||
* to direct devices to perform a geo-fencing check on selected alerts.
|
||||
*
|
||||
* WEA Handset Action Message(WHAM) requirements from ATIS-0700041 section 4
|
||||
* 1. The Warning Message contents of a WHAM shall be in Cell Broadcast(CB) data format as
|
||||
* defined in TS 23.041.
|
||||
* 2. The Warning Message Contents of WHAM shall be limited to one CB page(max 20 referenced
|
||||
* WEA messages).
|
||||
* 3. The broadcast area for a WHAM shall be the union of the broadcast areas of the referenced
|
||||
* WEA message.
|
||||
* @param pdu cell broadcast pdu, including the header
|
||||
* @return {@link GeoFencingTriggerMessage} instance
|
||||
*/
|
||||
public static GeoFencingTriggerMessage createGeoFencingTriggerMessage(byte[] pdu) {
|
||||
try {
|
||||
// Header length + 1(number of page). ATIS-0700041 define the number of page of
|
||||
// geo-fencing trigger message is 1.
|
||||
int whamOffset = SmsCbHeader.PDU_HEADER_LENGTH + 1;
|
||||
|
||||
BitStreamReader bitReader = new BitStreamReader(pdu, whamOffset);
|
||||
int type = bitReader.read(4);
|
||||
int length = bitReader.read(7);
|
||||
// Skip the remained 5 bits
|
||||
bitReader.skip();
|
||||
|
||||
int messageIdentifierCount = (length - 2) * 8 / 32;
|
||||
List<CellBroadcastIdentity> cbIdentifiers = new ArrayList<>();
|
||||
for (int i = 0; i < messageIdentifierCount; i++) {
|
||||
// Both messageIdentifier and serialNumber are 16 bits integers.
|
||||
// ATIS-0700041 Section 5.1.6
|
||||
int messageIdentifier = bitReader.read(16);
|
||||
int serialNumber = bitReader.read(16);
|
||||
cbIdentifiers.add(new CellBroadcastIdentity(messageIdentifier, serialNumber));
|
||||
}
|
||||
return new GeoFencingTriggerMessage(type, cbIdentifiers);
|
||||
} catch (Exception ex) {
|
||||
Slog.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Geometry> parseWarningAreaCoordinates(byte[] pdu, int wacOffset) {
|
||||
// little-endian
|
||||
int wacDataLength = (pdu[wacOffset + 1] << 8) | pdu[wacOffset];
|
||||
int offset = wacOffset + 2;
|
||||
|
||||
if (offset + wacDataLength > pdu.length) {
|
||||
throw new IllegalArgumentException("Invalid wac data, expected the length of pdu at"
|
||||
+ "least " + offset + wacDataLength + ", actual is " + pdu.length);
|
||||
}
|
||||
|
||||
BitStreamReader bitReader = new BitStreamReader(pdu, offset);
|
||||
|
||||
List<Geometry> geo = new ArrayList<>();
|
||||
int remainedBytes = wacDataLength;
|
||||
while (remainedBytes > 0) {
|
||||
int type = bitReader.read(4);
|
||||
int length = bitReader.read(10);
|
||||
remainedBytes -= length;
|
||||
// Skip the 2 remained bits
|
||||
bitReader.skip();
|
||||
|
||||
switch (type) {
|
||||
case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME:
|
||||
// TODO: handle the maximum wait time in cell broadcast provider.
|
||||
int maximumWaitTimeSec = bitReader.read(8);
|
||||
break;
|
||||
case CbGeoUtils.GEOMETRY_TYPE_POLYGON:
|
||||
List<LatLng> latLngs = new ArrayList<>();
|
||||
// Each coordinate is represented by 44 bits integer.
|
||||
// ATIS-0700041 5.2.4 Coordinate coding
|
||||
int n = (length - 2) * 8 / 44;
|
||||
for (int i = 0; i < n; i++) {
|
||||
latLngs.add(getLatLng(bitReader));
|
||||
}
|
||||
// Skip the padding bits
|
||||
bitReader.skip();
|
||||
geo.add(new Polygon(latLngs));
|
||||
break;
|
||||
case CbGeoUtils.GEOMETRY_TYPE_CIRCLE:
|
||||
LatLng center = getLatLng(bitReader);
|
||||
// radius = (wacRadius / 2^6). The unit of wacRadius is km, we use meter as the
|
||||
// distance unit during geo-fencing.
|
||||
// ATIS-0700041 5.2.5 radius coding
|
||||
double radius = (bitReader.read(20) * 1.0 / (1 << 6)) * 1000.0;
|
||||
geo.add(new Circle(center, radius));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported geoType = " + type);
|
||||
}
|
||||
}
|
||||
return geo;
|
||||
}
|
||||
|
||||
/**
|
||||
* The coordinate is (latitude, longitude), represented by a 44 bits integer.
|
||||
* The coding is defined in ATIS-0700041 5.2.4
|
||||
* @param bitReader
|
||||
* @return coordinate (latitude, longitude)
|
||||
*/
|
||||
private static LatLng getLatLng(BitStreamReader bitReader) {
|
||||
// wacLatitude = floor(((latitude + 90) / 180) * 2^22)
|
||||
// wacLongitude = floor(((longitude + 180) / 360) * 2^22)
|
||||
int wacLat = bitReader.read(22);
|
||||
int wacLng = bitReader.read(22);
|
||||
|
||||
// latitude = wacLatitude * 180 / 2^22 - 90
|
||||
// longitude = wacLongitude * 360 / 2^22 -180
|
||||
return new LatLng((wacLat * 180.0 / (1 << 22)) - 90, (wacLng * 360.0 / (1 << 22) - 180));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and unpack the UMTS body text according to the encoding in the data coding scheme.
|
||||
*
|
||||
* @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
|
||||
* @return a pair of string containing the language and body of the message in order
|
||||
*/
|
||||
private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) {
|
||||
int encoding;
|
||||
String language = null;
|
||||
boolean hasLanguageIndicator = false;
|
||||
int dataCodingScheme = header.getDataCodingScheme();
|
||||
private static Pair<String, String> parseUmtsBody(SmsCbHeader header, byte[] pdu) {
|
||||
// Payload may contain multiple pages
|
||||
int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
|
||||
String language = header.getDataCodingSchemeStructedData().language;
|
||||
|
||||
// Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
|
||||
// section 5.
|
||||
switch ((dataCodingScheme & 0xf0) >> 4) {
|
||||
case 0x00:
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
|
||||
break;
|
||||
|
||||
case 0x01:
|
||||
hasLanguageIndicator = true;
|
||||
if ((dataCodingScheme & 0x0f) == 0x01) {
|
||||
encoding = SmsConstants.ENCODING_16BIT;
|
||||
} else {
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
|
||||
break;
|
||||
|
||||
case 0x03:
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
switch ((dataCodingScheme & 0x0c) >> 2) {
|
||||
case 0x01:
|
||||
encoding = SmsConstants.ENCODING_8BIT;
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
encoding = SmsConstants.ENCODING_16BIT;
|
||||
break;
|
||||
|
||||
case 0x00:
|
||||
default:
|
||||
encoding = SmsConstants.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 = SmsConstants.ENCODING_8BIT;
|
||||
} else {
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Reserved values are to be treated as 7-bit
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
break;
|
||||
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");
|
||||
}
|
||||
|
||||
if (header.isUmtsFormat()) {
|
||||
// Payload may contain multiple pages
|
||||
int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
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");
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
Pair<String, String> p = unpackBody(pdu, offset, length,
|
||||
header.getDataCodingSchemeStructedData());
|
||||
language = p.first;
|
||||
sb.append(p.second);
|
||||
}
|
||||
return new Pair(language, sb.toString());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack body text from the pdu using the given encoding, position and
|
||||
* length within the pdu
|
||||
* Parse and unpack the GSM body text according to the encoding in the data coding scheme.
|
||||
* @param header the message header to use
|
||||
* @param pdu the PDU to decode
|
||||
* @return a pair of string containing the language and body of the message in order
|
||||
*/
|
||||
private static Pair<String, String> parseGsmBody(SmsCbHeader header, byte[] pdu) {
|
||||
// Payload is one single page
|
||||
int offset = SmsCbHeader.PDU_HEADER_LENGTH;
|
||||
int length = pdu.length - offset;
|
||||
return unpackBody(pdu, offset, length, header.getDataCodingSchemeStructedData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param dcs data coding scheme
|
||||
* @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) {
|
||||
private static Pair<String, String> unpackBody(byte[] pdu, int offset, int length,
|
||||
DataCodingScheme dcs) {
|
||||
String body = null;
|
||||
|
||||
switch (encoding) {
|
||||
String language = dcs.language;
|
||||
switch (dcs.encoding) {
|
||||
case SmsConstants.ENCODING_7BIT:
|
||||
body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
|
||||
|
||||
if (hasLanguageIndicator && body != null && body.length() > 2) {
|
||||
if (dcs.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);
|
||||
@@ -296,7 +347,7 @@ public class GsmSmsCbMessage {
|
||||
break;
|
||||
|
||||
case SmsConstants.ENCODING_16BIT:
|
||||
if (hasLanguageIndicator && pdu.length >= offset + 2) {
|
||||
if (dcs.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);
|
||||
@@ -330,4 +381,105 @@ public class GsmSmsCbMessage {
|
||||
|
||||
return new Pair<String, String>(language, body);
|
||||
}
|
||||
|
||||
/** A class use to facilitate the processing of bits stream data. */
|
||||
private static final class BitStreamReader {
|
||||
/** The bits stream represent by a bytes array. */
|
||||
private final byte[] mData;
|
||||
|
||||
/** The offset of the current byte. */
|
||||
private int mCurrentOffset;
|
||||
|
||||
/**
|
||||
* The remained bits of the current byte which have not been read. The most significant
|
||||
* will be read first, so the remained bits are always the least significant bits.
|
||||
*/
|
||||
private int mRemainedBit;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param data bit stream data represent by byte array.
|
||||
* @param offset the offset of the first byte.
|
||||
*/
|
||||
BitStreamReader(byte[] data, int offset) {
|
||||
mData = data;
|
||||
mCurrentOffset = offset;
|
||||
mRemainedBit = 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the first {@code count} bits.
|
||||
* @param count the number of bits need to read
|
||||
* @return {@code bits} represent by an 32-bits integer, therefore {@code count} must be no
|
||||
* greater than 32.
|
||||
*/
|
||||
public int read(int count) throws IndexOutOfBoundsException {
|
||||
int val = 0;
|
||||
while (count > 0) {
|
||||
if (count >= mRemainedBit) {
|
||||
val <<= mRemainedBit;
|
||||
val |= mData[mCurrentOffset] & ((1 << mRemainedBit) - 1);
|
||||
count -= mRemainedBit;
|
||||
mRemainedBit = 8;
|
||||
++mCurrentOffset;
|
||||
} else {
|
||||
val <<= count;
|
||||
val |= (mData[mCurrentOffset] & ((1 << mRemainedBit) - 1))
|
||||
>> (mRemainedBit - count);
|
||||
mRemainedBit -= count;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the current bytes if the remained bits is less than 8. This is useful when
|
||||
* processing the padding or reserved bits.
|
||||
*/
|
||||
public void skip() {
|
||||
if (mRemainedBit < 8) {
|
||||
mRemainedBit = 8;
|
||||
++mCurrentOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final class GeoFencingTriggerMessage {
|
||||
/**
|
||||
* Indicate the list of active alerts share their warning area coordinates which means the
|
||||
* broadcast area is the union of the broadcast areas of the active alerts in this list.
|
||||
*/
|
||||
public static final int TYPE_ACTIVE_ALERT_SHARE_WAC = 2;
|
||||
|
||||
public final int type;
|
||||
public final List<CellBroadcastIdentity> cbIdentifiers;
|
||||
|
||||
GeoFencingTriggerMessage(int type, @NonNull List<CellBroadcastIdentity> cbIdentifiers) {
|
||||
this.type = type;
|
||||
this.cbIdentifiers = cbIdentifiers;
|
||||
}
|
||||
|
||||
boolean shouldShareBroadcastArea() {
|
||||
return type == TYPE_ACTIVE_ALERT_SHARE_WAC;
|
||||
}
|
||||
|
||||
static final class CellBroadcastIdentity {
|
||||
public final int messageIdentifier;
|
||||
public final int serialNumber;
|
||||
CellBroadcastIdentity(int messageIdentifier, int serialNumber) {
|
||||
this.messageIdentifier = messageIdentifier;
|
||||
this.serialNumber = serialNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String identifiers = cbIdentifiers.stream()
|
||||
.map(cbIdentifier ->String.format("(msgId = %d, serial = %d)",
|
||||
cbIdentifier.messageIdentifier, cbIdentifier.serialNumber))
|
||||
.collect(Collectors.joining(","));
|
||||
return "triggerType=" + type + " identifiers=" + identifiers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,9 +215,11 @@ public class SmsCbConstants {
|
||||
public static final int MESSAGE_ID_CMAS_ALERT_STATE_LOCAL_TEST_LANGUAGE
|
||||
= 0x112F; // 4399
|
||||
|
||||
/** End of CMAS Message Identifier range (including future extensions). */
|
||||
public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER
|
||||
= 0x112F; // 4399
|
||||
/** CMAS Message Identifier for CMAS geo fencing trigger message. */
|
||||
public static final int MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER = 0x1130; // 4440
|
||||
|
||||
/** End of CMAS Message Identifier range. */
|
||||
public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER = MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER;
|
||||
|
||||
/** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
|
||||
public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER
|
||||
|
||||
@@ -19,7 +19,10 @@ package com.android.internal.telephony.gsm;
|
||||
import android.telephony.SmsCbCmasInfo;
|
||||
import android.telephony.SmsCbEtwsInfo;
|
||||
|
||||
import com.android.internal.telephony.SmsConstants;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by
|
||||
@@ -32,6 +35,39 @@ import java.util.Arrays;
|
||||
* The raw PDU is no longer sent to SMS CB applications.
|
||||
*/
|
||||
public class SmsCbHeader {
|
||||
/**
|
||||
* Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
|
||||
*/
|
||||
private static final String[] LANGUAGE_CODES_GROUP_0 = {
|
||||
Locale.GERMAN.getLanguage(), // German
|
||||
Locale.ENGLISH.getLanguage(), // English
|
||||
Locale.ITALIAN.getLanguage(), // Italian
|
||||
Locale.FRENCH.getLanguage(), // French
|
||||
new Locale("es").getLanguage(), // Spanish
|
||||
new Locale("nl").getLanguage(), // Dutch
|
||||
new Locale("sv").getLanguage(), // Swedish
|
||||
new Locale("da").getLanguage(), // Danish
|
||||
new Locale("pt").getLanguage(), // Portuguese
|
||||
new Locale("fi").getLanguage(), // Finnish
|
||||
new Locale("nb").getLanguage(), // Norwegian
|
||||
new Locale("el").getLanguage(), // Greek
|
||||
new Locale("tr").getLanguage(), // Turkish
|
||||
new Locale("hu").getLanguage(), // Hungarian
|
||||
new Locale("pl").getLanguage(), // Polish
|
||||
null
|
||||
};
|
||||
|
||||
/**
|
||||
* Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
|
||||
*/
|
||||
private static final String[] LANGUAGE_CODES_GROUP_2 = {
|
||||
new Locale("cs").getLanguage(), // Czech
|
||||
new Locale("he").getLanguage(), // Hebrew
|
||||
new Locale("ar").getLanguage(), // Arabic
|
||||
new Locale("ru").getLanguage(), // Russian
|
||||
new Locale("is").getLanguage(), // Icelandic
|
||||
null, null, null, null, null, null, null, null, null, null, null
|
||||
};
|
||||
|
||||
/**
|
||||
* Length of SMS-CB header
|
||||
@@ -84,6 +120,8 @@ public class SmsCbHeader {
|
||||
|
||||
private final int mFormat;
|
||||
|
||||
private DataCodingScheme mDataCodingSchemeStructedData;
|
||||
|
||||
/** ETWS warning notification info. */
|
||||
private final SmsCbEtwsInfo mEtwsInfo;
|
||||
|
||||
@@ -162,6 +200,10 @@ public class SmsCbHeader {
|
||||
mNrOfPages = 1;
|
||||
}
|
||||
|
||||
if (mDataCodingScheme != -1) {
|
||||
mDataCodingSchemeStructedData = new DataCodingScheme(mDataCodingScheme);
|
||||
}
|
||||
|
||||
if (isEtwsMessage()) {
|
||||
boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
|
||||
boolean activatePopup = isEtwsPopupAlert();
|
||||
@@ -199,6 +241,10 @@ public class SmsCbHeader {
|
||||
return mDataCodingScheme;
|
||||
}
|
||||
|
||||
DataCodingScheme getDataCodingSchemeStructedData() {
|
||||
return mDataCodingSchemeStructedData;
|
||||
}
|
||||
|
||||
int getPageIndex() {
|
||||
return mPageIndex;
|
||||
}
|
||||
@@ -448,4 +494,93 @@ public class SmsCbHeader {
|
||||
+ ", DCS=0x" + Integer.toHexString(mDataCodingScheme)
|
||||
+ ", page " + mPageIndex + " of " + mNrOfPages + '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* CBS Data Coding Scheme.
|
||||
* Reference: 3GPP TS 23.038 version 15.0.0 section #5, CBS Data Coding Scheme
|
||||
*/
|
||||
public static final class DataCodingScheme {
|
||||
public final int encoding;
|
||||
public final String language;
|
||||
public final boolean hasLanguageIndicator;
|
||||
|
||||
public DataCodingScheme(int dataCodingScheme) {
|
||||
int encoding = 0;
|
||||
String language = null;
|
||||
boolean hasLanguageIndicator = false;
|
||||
|
||||
// Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
|
||||
// section 5.
|
||||
switch ((dataCodingScheme & 0xf0) >> 4) {
|
||||
case 0x00:
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
|
||||
break;
|
||||
|
||||
case 0x01:
|
||||
hasLanguageIndicator = true;
|
||||
if ((dataCodingScheme & 0x0f) == 0x01) {
|
||||
encoding = SmsConstants.ENCODING_16BIT;
|
||||
} else {
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
|
||||
break;
|
||||
|
||||
case 0x03:
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
switch ((dataCodingScheme & 0x0c) >> 2) {
|
||||
case 0x01:
|
||||
encoding = SmsConstants.ENCODING_8BIT;
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
encoding = SmsConstants.ENCODING_16BIT;
|
||||
break;
|
||||
|
||||
case 0x00:
|
||||
default:
|
||||
encoding = SmsConstants.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 = SmsConstants.ENCODING_8BIT;
|
||||
} else {
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Reserved values are to be treated as 7-bit
|
||||
encoding = SmsConstants.ENCODING_7BIT;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
this.encoding = encoding;
|
||||
this.language = language;
|
||||
this.hasLanguageIndicator = hasLanguageIndicator;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user