diff options
| author | 2020-02-04 14:57:45 +0000 | |
|---|---|---|
| committer | 2020-02-04 14:57:45 +0000 | |
| commit | 85bd3433a828b833ecbdb56577c58e04ae6035fb (patch) | |
| tree | 36c3eab472e8ecd7f77e243e3bf1351c06bfb4de | |
| parent | 5f603929dad7c3782f8daf654edd25b423e400d1 (diff) | |
| parent | 171665a1bb1d83571401b4432f7a7d027be42bce (diff) | |
Merge "Move GsmSmsCbMessage to CBReceiver test app" into stage-aosp-master
| -rw-r--r-- | telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java | 521 |
1 files changed, 0 insertions, 521 deletions
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java deleted file mode 100644 index 515687fdfdf0..000000000000 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java +++ /dev/null @@ -1,521 +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 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 android.annotation.NonNull; -import android.content.Context; -import android.content.res.Resources; -import android.telephony.CbGeoUtils; -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.SubscriptionManager; -import android.util.Pair; -import android.util.Slog; - -import com.android.internal.R; -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.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. - */ - private 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 - * @slotIndex slotIndex for which received sms cb message - */ - 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, - getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()), - SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(), - header.getCmasInfo(), 0, null /* geometries */, 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. - 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(), 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, sb.toString(), priority, - header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */, - 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) { - Slog.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString()); - 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) { - 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); - - 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: - throw new IllegalArgumentException("Unsupported geoType = " + type); - } - } - 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 SmsConstants.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 SmsConstants.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; - } - - 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; - } - } -} |