549 lines
24 KiB
Java
549 lines
24 KiB
Java
/*
|
|
* 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.cellbroadcastservice;
|
|
|
|
import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
|
|
import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
|
|
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 static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_GEO_FENCING_DATA;
|
|
import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_UMTS_INVALID_WAC;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.content.Context;
|
|
import android.content.res.Resources;
|
|
import android.telephony.CbGeoUtils.Circle;
|
|
import android.telephony.CbGeoUtils.Geometry;
|
|
import android.telephony.CbGeoUtils.LatLng;
|
|
import android.telephony.CbGeoUtils.Polygon;
|
|
import android.telephony.SmsCbLocation;
|
|
import android.telephony.SmsCbMessage;
|
|
import android.telephony.SmsMessage;
|
|
import android.telephony.SubscriptionManager;
|
|
import android.util.Log;
|
|
import android.util.Pair;
|
|
|
|
import com.android.cellbroadcastservice.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
|
|
import com.android.cellbroadcastservice.SmsCbHeader.DataCodingScheme;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
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 {
|
|
private static final String TAG = GsmSmsCbMessage.class.getSimpleName();
|
|
|
|
private static final char CARRIAGE_RETURN = 0x0d;
|
|
|
|
private static final int PDU_BODY_PAGE_LENGTH = 82;
|
|
|
|
/** Utility class with only static methods. */
|
|
private GsmSmsCbMessage() { }
|
|
|
|
/**
|
|
* Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
|
|
* so we have to show the pre-built messages to the user.
|
|
*
|
|
* @param context Device context
|
|
* @param category ETWS message category defined in SmsCbConstants
|
|
* @return ETWS text message in string. Return an empty string if no match.
|
|
*/
|
|
@VisibleForTesting
|
|
public static String getEtwsPrimaryMessage(Context context, int category) {
|
|
final Resources r = context.getResources();
|
|
switch (category) {
|
|
case ETWS_WARNING_TYPE_EARTHQUAKE:
|
|
return r.getString(R.string.etws_primary_default_message_earthquake);
|
|
case ETWS_WARNING_TYPE_TSUNAMI:
|
|
return r.getString(R.string.etws_primary_default_message_tsunami);
|
|
case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
|
|
return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
|
|
case ETWS_WARNING_TYPE_TEST_MESSAGE:
|
|
return r.getString(R.string.etws_primary_default_message_test);
|
|
case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
|
|
return r.getString(R.string.etws_primary_default_message_others);
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new SmsCbMessage object from a header object plus one or more received PDUs.
|
|
*
|
|
* @param pdus PDU bytes
|
|
*/
|
|
public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
|
|
SmsCbLocation location, byte[][] pdus, int slotIndex)
|
|
throws IllegalArgumentException {
|
|
SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
|
|
Context.TELEPHONY_SUBSCRIPTION_SERVICE);
|
|
int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
|
|
int[] subIds = sm.getSubscriptionIds(slotIndex);
|
|
if (subIds != null && subIds.length > 0) {
|
|
subId = subIds[0];
|
|
}
|
|
|
|
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,
|
|
// message identifier, warning type, and warning security information.
|
|
// There is no field for the content/text so we get the text from the resources.
|
|
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
|
|
header.getSerialNumber(), location, header.getServiceCategory(), null,
|
|
header.getDataCodingScheme(), getEtwsPrimaryMessage(context,
|
|
header.getEtwsInfo().getWarningType()), SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
|
|
header.getEtwsInfo(), header.getCmasInfo(), 0, null, receivedTimeMillis,
|
|
slotIndex, subId);
|
|
} 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;
|
|
int maximumWaitingTimeSec = 255;
|
|
if (pdu.length > wacDataOffset) {
|
|
try {
|
|
Pair<Integer, List<Geometry>> wac = parseWarningAreaCoordinates(pdu,
|
|
wacDataOffset);
|
|
maximumWaitingTimeSec = wac.first;
|
|
geometries = wac.second;
|
|
} 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.
|
|
Log.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, header.getDataCodingScheme(), body,
|
|
priority, header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec,
|
|
geometries, receivedTimeMillis, slotIndex, subId);
|
|
} else {
|
|
String language = null;
|
|
StringBuilder sb = new StringBuilder();
|
|
for (byte[] pdu : pdus) {
|
|
Pair<String, String> p = parseGsmBody(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, header.getDataCodingScheme(),
|
|
sb.toString(), priority, header.getEtwsInfo(), header.getCmasInfo(), 0, null,
|
|
receivedTimeMillis, slotIndex, subId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
final String errorMessage = "create geo-fencing trigger failed, ex = " + ex.toString();
|
|
Log.e(TAG, errorMessage);
|
|
CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
|
|
CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_GEO_FENCING_DATA,
|
|
errorMessage);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse the broadcast area and maximum wait time from the Warning Area Coordinates TLV.
|
|
*
|
|
* @param pdu Warning Area Coordinates TLV.
|
|
* @param wacOffset the offset of Warning Area Coordinates TLV.
|
|
* @return a pair with the first element is maximum wait time and the second is the broadcast
|
|
* area. The default value of the maximum wait time is 255 which means use the device default
|
|
* value.
|
|
*/
|
|
private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates(
|
|
byte[] pdu, int wacOffset) {
|
|
// little-endian
|
|
int wacDataLength = ((pdu[wacOffset + 1] & 0xff) << 8) | (pdu[wacOffset] & 0xff);
|
|
int offset = wacOffset + 2;
|
|
|
|
if (offset + wacDataLength > pdu.length) {
|
|
IllegalArgumentException ex = new IllegalArgumentException(
|
|
"Invalid wac data, expected the length of pdu at least "
|
|
+ (offset + wacDataLength) + ", actual is " + pdu.length);
|
|
CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
|
|
CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_UMTS_INVALID_WAC,
|
|
ex.toString());
|
|
throw ex;
|
|
}
|
|
|
|
BitStreamReader bitReader = new BitStreamReader(pdu, offset);
|
|
|
|
int maximumWaitTimeSec = SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET;
|
|
|
|
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:
|
|
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:
|
|
IllegalArgumentException ex = new IllegalArgumentException(
|
|
"Unsupported geoType = " + type);
|
|
CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
|
|
CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_UMTS_INVALID_WAC,
|
|
ex.toString());
|
|
throw ex;
|
|
}
|
|
}
|
|
return new Pair(maximumWaitTimeSec, 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 string containing the language and body of the message in order
|
|
*/
|
|
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;
|
|
|
|
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, offset, length,
|
|
header.getDataCodingSchemeStructedData());
|
|
language = p.first;
|
|
sb.append(p.second);
|
|
}
|
|
return new Pair(language, sb.toString());
|
|
|
|
}
|
|
|
|
/**
|
|
* 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 offset Position of the first byte to unpack
|
|
* @param length Number of bytes to unpack
|
|
* @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 offset,
|
|
int length, DataCodingScheme dcs) {
|
|
String body = null;
|
|
|
|
String language = dcs.language;
|
|
switch (dcs.encoding) {
|
|
case SmsMessage.ENCODING_7BIT:
|
|
body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
|
|
|
|
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);
|
|
body = body.substring(3);
|
|
}
|
|
break;
|
|
|
|
case SmsMessage.ENCODING_8BIT:
|
|
// Support decoding the pdu as pack GSM 8-bit (a GSM alphabet string that's stored
|
|
// in 8-bit unpacked format) characters.
|
|
body = GsmAlphabet.gsm8BitUnpackedToString(pdu, offset, length);
|
|
break;
|
|
|
|
case SmsMessage.ENCODING_16BIT:
|
|
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);
|
|
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);
|
|
}
|
|
|
|
/** 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Part of a GSM SMS cell broadcast message which may trigger geo-fencing logic.
|
|
* @hide
|
|
*/
|
|
public 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;
|
|
}
|
|
|
|
/**
|
|
* Whether the trigger message indicates that the broadcast areas are shared between all
|
|
* active alerts.
|
|
* @return true if broadcast areas are to be shared
|
|
*/
|
|
boolean shouldShareBroadcastArea() {
|
|
return type == TYPE_ACTIVE_ALERT_SHARE_WAC;
|
|
}
|
|
|
|
/**
|
|
* The GSM cell broadcast identity
|
|
*/
|
|
@VisibleForTesting
|
|
public 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;
|
|
}
|
|
}
|
|
}
|