Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 66040bbb authored by Jake Hamby's avatar Jake Hamby
Browse files

Fixes for SMS Cell Broadcast support.

Add support for ETWS primary notification messages.
Add method for easy concatenation of GSM multi-part broadcasts.
Add test cases for SmsCbHeader, SmsCbMessage and IntRangeManager.

Change-Id: Ifc646a011e79ad6c7eace9afcf84b1216eb42b7a
parent d6ce6791
Loading
Loading
Loading
Loading
+15 −12
Original line number Diff line number Diff line
@@ -22,18 +22,6 @@ package android.telephony;
 * {@hide}
 */
public interface SmsCbConstants {
    /** Cell wide immediate geographical scope */
    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;

    /** PLMN wide geographical scope */
    public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;

    /** Location / service area wide geographical scope */
    public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;

    /** Cell wide geographical scope */
    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;

    /** Start of PWS Message Identifier range (includes ETWS and CMAS). */
    public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER = 0x1100;

@@ -111,4 +99,19 @@ public interface SmsCbConstants {

    /** ETWS message code flag to activate the emergency user alert. */
    public static final int MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT          = 0x200;

    /** 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;
}
+118 −1
Original line number Diff line number Diff line
@@ -16,9 +16,11 @@

package android.telephony;

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.gsm.SmsCbHeader;

import java.io.UnsupportedEncodingException;
@@ -93,10 +95,27 @@ public class SmsCbMessage {

    private String mBody;

    /** Timestamp of ETWS primary notification with security. */
    private long mPrimaryNotificationTimestamp;

    /** 43 byte digital signature of ETWS primary notification with security. */
    private byte[] mPrimaryNotificationDigitalSignature;

    private SmsCbMessage(byte[] pdu) throws IllegalArgumentException {
        mHeader = new SmsCbHeader(pdu);
        if (mHeader.format == SmsCbHeader.FORMAT_ETWS_PRIMARY) {
            mBody = "ETWS";
            // ETWS primary notification with security is 56 octets in length
            if (pdu.length >= SmsCbHeader.PDU_LENGTH_ETWS) {
                mPrimaryNotificationTimestamp = getTimestampMillis(pdu);
                mPrimaryNotificationDigitalSignature = new byte[43];
                // digital signature starts after 6 byte header and 7 byte timestamp
                System.arraycopy(pdu, 13, mPrimaryNotificationDigitalSignature, 0, 43);
            }
        } else {
            parseBody(pdu);
        }
    }

    /**
     * Return the geographical scope of this message, one of
@@ -156,6 +175,55 @@ public class SmsCbMessage {
        return mHeader.updateNumber;
    }

    /**
     * Get the format of this message.
     * @return {@link SmsCbHeader#FORMAT_GSM}, {@link SmsCbHeader#FORMAT_UMTS}, or
     *         {@link SmsCbHeader#FORMAT_ETWS_PRIMARY}
     */
    public int getMessageFormat() {
        return mHeader.format;
    }

    /**
     * For ETWS primary notifications, return the emergency user alert flag.
     * @return true to notify terminal to activate emergency user alert; false otherwise
     */
    public boolean getEtwsEmergencyUserAlert() {
        return mHeader.etwsEmergencyUserAlert;
    }

    /**
     * For ETWS primary notifications, return the popup flag.
     * @return true to notify terminal to activate display popup; false otherwise
     */
    public boolean getEtwsPopup() {
        return mHeader.etwsPopup;
    }

    /**
     * For ETWS primary notifications, return the warning type.
     * @return a value such as {@link SmsCbConstants#ETWS_WARNING_TYPE_EARTHQUAKE}
     */
    public int getEtwsWarningType() {
        return mHeader.etwsWarningType;
    }

    /**
     * For ETWS primary notifications, return the Warning-Security-Information timestamp.
     * @return a timestamp in System.currentTimeMillis() format.
     */
    public long getEtwsSecurityTimestamp() {
        return mPrimaryNotificationTimestamp;
    }

    /**
     * For ETWS primary notifications, return the 43 byte digital signature.
     * @return a byte array containing a copy of the digital signature
     */
    public byte[] getEtwsSecuritySignature() {
        return mPrimaryNotificationDigitalSignature.clone();
    }

    /**
     * Parse and unpack the body text according to the encoding in the DCS.
     * After completing successfully this method will have assigned the body
@@ -334,6 +402,55 @@ public class SmsCbMessage {
        return body;
    }

    /**
     * Parses an ETWS primary notification timestamp and returns a currentTimeMillis()-style
     * timestamp. Copied from com.android.internal.telephony.gsm.SmsMessage.
     * @param pdu the ETWS primary notification PDU to decode
     * @return the UTC timestamp from the Warning-Security-Information parameter
     */
    private long getTimestampMillis(byte[] pdu) {
        // Timestamp starts after CB header, in pdu[6]
        int year = IccUtils.gsmBcdByteToInt(pdu[6]);
        int month = IccUtils.gsmBcdByteToInt(pdu[7]);
        int day = IccUtils.gsmBcdByteToInt(pdu[8]);
        int hour = IccUtils.gsmBcdByteToInt(pdu[9]);
        int minute = IccUtils.gsmBcdByteToInt(pdu[10]);
        int second = IccUtils.gsmBcdByteToInt(pdu[11]);

        // For the timezone, the most significant bit of the
        // least significant nibble is the sign byte
        // (meaning the max range of this field is 79 quarter-hours,
        // which is more than enough)

        byte tzByte = pdu[12];

        // Mask out sign bit.
        int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));

        timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;

        Time time = new Time(Time.TIMEZONE_UTC);

        // It's 2006.  Should I really support years < 2000?
        time.year = year >= 90 ? year + 1900 : year + 2000;
        time.month = month - 1;
        time.monthDay = day;
        time.hour = hour;
        time.minute = minute;
        time.second = second;

        // Timezone offset is in quarter hours.
        return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
    }

    /**
     * Append text to the message body. This is used to concatenate multi-page GSM broadcasts.
     * @param body the text to append to this message
     */
    public void appendToBody(String body) {
        mBody = mBody + body;
    }

    @Override
    public String toString() {
        return "SmsCbMessage{" + mHeader.toString() + ", language=" + mLanguage +
+65 −57
Original line number Diff line number Diff line
@@ -290,44 +290,45 @@ public abstract class IntRangeManager {
                    return true;
                } else {
                    // find last range that can coalesce into the new combined range
                    for (int endIndex = startIndex+1; endIndex < len; endIndex++) {
                        IntRange endRange = mRanges.get(endIndex);
                        if ((endId + 1) < endRange.startId) {
                    int endIndex = startIndex;
                    for (int testIndex = startIndex+1; testIndex < len; testIndex++) {
                        IntRange testRange = mRanges.get(testIndex);
                        if ((endId + 1) < testRange.startId) {
                            break;
                        } else {
                            endIndex = testIndex;
                        }
                    }
                    // no adjacent IntRanges to combine
                    if (endIndex == startIndex) {
                        // add range from range.endId+1 to endId,
                        // values from startId to range.endId are already enabled
                        if (tryAddSingleRange(range.endId + 1, endId, true)) {
                            range.endId = endId;
                                // insert new ClientRange in place
                            range.insert(new ClientRange(startId, endId, client));
                                // coalesce range with following ranges up to endIndex-1
                                // remove each range after adding its elements, so the index
                                // of the next range to join is always startIndex+1.
                                // i is the index if no elements were removed: we only care
                                // about the number of loop iterations, not the value of i.
                                int joinIndex = startIndex + 1;
                                for (int i = joinIndex; i < endIndex; i++) {
                                    IntRange joinRange = mRanges.get(joinIndex);
                                    range.clients.addAll(joinRange.clients);
                                    mRanges.remove(joinRange);
                                }
                            return true;
                        } else {
                            return false;   // failed to update radio
                        }
                        } else if (endId <= endRange.endId) {
                            // add range from range.endId+1 to start of last overlapping range,
                            // values from endRange.startId to endId are already enabled
                            if (tryAddSingleRange(range.endId + 1, endRange.startId - 1, true)) {
                                range.endId = endRange.endId;
                    }
                    // get last range to coalesce into start range
                    IntRange endRange = mRanges.get(endIndex);
                    // Values from startId to range.endId have already been enabled.
                    // if endId > endRange.endId, then enable range from range.endId+1 to endId,
                    // else enable range from range.endId+1 to endRange.startId-1, because
                    // values from endRange.startId to endId have already been added.
                    int newRangeEndId = (endId <= endRange.endId) ? endRange.startId - 1 : endId;
                    if (tryAddSingleRange(range.endId + 1, newRangeEndId, true)) {
                        range.endId = endId;
                        // insert new ClientRange in place
                        range.insert(new ClientRange(startId, endId, client));
                                // coalesce range with following ranges up to endIndex
                        // coalesce range with following ranges up to endIndex-1
                        // remove each range after adding its elements, so the index
                                // of the next range to join is always startIndex+1.
                                // i is the index if no elements were removed: we only care
                        // of the next range to join is always startIndex+1 (joinIndex).
                        // i is the index if no elements had been removed: we only care
                        // about the number of loop iterations, not the value of i.
                        int joinIndex = startIndex + 1;
                                for (int i = joinIndex; i <= endIndex; i++) {
                        for (int i = joinIndex; i < endIndex; i++) {
                            IntRange joinRange = mRanges.get(joinIndex);
                            range.clients.addAll(joinRange.clients);
                            mRanges.remove(joinRange);
@@ -339,8 +340,6 @@ public abstract class IntRangeManager {
                }
            }
        }
            }
        }

        // append new range after existing IntRanges
        if (tryAddSingleRange(startId, endId, true)) {
@@ -435,6 +434,8 @@ public abstract class IntRangeManager {
                                addRange(range.startId, nextStartId - 1, false);
                                rangeCopy.startId = nextStartId;
                            }
                            // init largestEndId
                            largestEndId = clients.get(1).endId;
                        }

                        // go through remaining ClientRanges, creating new IntRanges when
@@ -442,7 +443,6 @@ public abstract class IntRangeManager {
                        // remove the original IntRange and append newRanges to mRanges.
                        // Otherwise, leave the original IntRange in mRanges and return false.
                        ArrayList<IntRange> newRanges = new ArrayList<IntRange>();
                        newRanges.add(rangeCopy);

                        IntRange currentRange = rangeCopy;
                        for (int nextIndex = crIndex + 1; nextIndex < crLength; nextIndex++) {
@@ -454,6 +454,7 @@ public abstract class IntRangeManager {
                                }
                                addRange(largestEndId + 1, nextCr.startId - 1, false);
                                currentRange.endId = largestEndId;
                                newRanges.add(currentRange);
                                currentRange = new IntRange(nextCr);
                            } else {
                                currentRange.clients.add(nextCr);
@@ -463,18 +464,25 @@ public abstract class IntRangeManager {
                            }
                        }

                        if (updateStarted) {
                            if (!finishUpdate()) {
                        // remove any channels between largestEndId and endId
                        if (largestEndId < endId) {
                            if (!updateStarted) {
                                startUpdate();
                                updateStarted = true;
                            }
                            addRange(largestEndId + 1, endId, false);
                            currentRange.endId = largestEndId;
                        }
                        newRanges.add(currentRange);

                        if (updateStarted && !finishUpdate()) {
                            return false;   // failed to update radio
                            } else {
                                // remove the original IntRange and insert newRanges in place.
                                mRanges.remove(crIndex);
                                mRanges.addAll(crIndex, newRanges);
                                return true;
                        }
                        } else {

                        // replace the original IntRange with newRanges
                        mRanges.remove(i);
                        mRanges.addAll(i, newRanges);
                        return true;
                        }
                    } else {
                        // not the ClientRange to remove; save highest end ID seen so far
                        if (cr.endId > largestEndId) {
+36 −2
Original line number Diff line number Diff line
@@ -34,6 +34,11 @@ public class SmsCbHeader implements SmsCbConstants {
     */
    public static final int FORMAT_UMTS = 2;

    /**
     * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
     */
    public static final int FORMAT_ETWS_PRIMARY = 3;

    /**
     * Message type value as defined in 3gpp TS 25.324, section 11.1.
     */
@@ -42,7 +47,12 @@ public class SmsCbHeader implements SmsCbConstants {
    /**
     * 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
     */
    public static final int PDU_LENGTH_ETWS = 56;

    public final int geographicalScope;

@@ -60,12 +70,30 @@ public class SmsCbHeader implements SmsCbConstants {

    public final int format;

    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) {
            throw new IllegalArgumentException("Illegal PDU");
        }

        if (pdu.length <= PDU_LENGTH_GSM) {
        if (pdu.length <= PDU_LENGTH_ETWS) {
            format = FORMAT_ETWS_PRIMARY;
            geographicalScope = -1; //not applicable
            messageCode = -1;
            updateNumber = -1;
            messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
            dataCodingScheme = -1;
            pageIndex = -1;
            nrOfPages = -1;
            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;
@@ -85,6 +113,9 @@ public class SmsCbHeader implements SmsCbConstants {

            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
@@ -107,6 +138,9 @@ public class SmsCbHeader implements SmsCbConstants {
            // actual payload may contain several pages.
            pageIndex = 1;
            nrOfPages = 1;
            etwsEmergencyUserAlert = false;
            etwsPopup = false;
            etwsWarningType = -1;
        }
    }

+712 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading