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

Commit ab79ee4a authored by Jake Hamby's avatar Jake Hamby Committed by Jean-Baptiste Queru
Browse files

Enable full support for SMS Cell Broadcast.

Implement full support for SMS Cell Broadcast (3GPP TS 23.041).
Includes support for ETWS and CMAS emergency message types.
Includes GSM and UMTS support (CDMA will be added later).

Note: the change to GsmAlphabet.java is only necessary if the
SMS national languages support patch has been applied. If that
change has not been applied, then the changes to GsmAlphabet.java
in this patch set can safely be ignored.

Change-Id: Ia0362c53695b8ef9a0982f558f1cffa912def34b
parent 8050a02b
Loading
Loading
Loading
Loading
+42 −2
Original line number Diff line number Diff line
@@ -541,7 +541,7 @@ public final class Telephony {
             * values:</p>
             *
             * <ul>
             *   <li><em>pdus</em> - An Object[] od byte[]s containing the PDUs
             *   <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
             *   that make up the message.</li>
             * </ul>
             *
@@ -575,6 +575,46 @@ public final class Telephony {
            public static final String WAP_PUSH_RECEIVED_ACTION =
                    "android.provider.Telephony.WAP_PUSH_RECEIVED";

            /**
             * Broadcast Action: A new Cell Broadcast message has been received
             * by the device. The intent will have the following extra
             * values:</p>
             *
             * <ul>
             *   <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
             *   that make up the message.</li>
             * </ul>
             *
             * <p>The extra values can be extracted using
             * {@link #getMessagesFromIntent(Intent)}.</p>
             *
             * <p>If a BroadcastReceiver encounters an error while processing
             * this intent it should set the result code appropriately.</p>
             */
            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
            public static final String SMS_CB_RECEIVED_ACTION =
                    "android.provider.Telephony.SMS_CB_RECEIVED";

            /**
             * Broadcast Action: A new Emergency Broadcast message has been received
             * by the device. The intent will have the following extra
             * values:</p>
             *
             * <ul>
             *   <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
             *   that make up the message.</li>
             * </ul>
             *
             * <p>The extra values can be extracted using
             * {@link #getMessagesFromIntent(Intent)}.</p>
             *
             * <p>If a BroadcastReceiver encounters an error while processing
             * this intent it should set the result code appropriately.</p>
             */
            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
            public static final String SMS_EMERGENCY_CB_RECEIVED_ACTION =
                    "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";

            /**
             * Broadcast Action: The SIM storage for SMS messages is full.  If
             * space is not freed, messages targeted for the SIM (class 2) may
@@ -608,7 +648,7 @@ public final class Telephony {
             * @param intent the intent to read from
             * @return an array of SmsMessages for the PDUs
             */
            public static final SmsMessage[] getMessagesFromIntent(
            public static SmsMessage[] getMessagesFromIntent(
                    Intent intent) {
                Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
                byte[][] pduObjs = new byte[messages.length][];
+9 −0
Original line number Diff line number Diff line
@@ -150,6 +150,15 @@
        android:label="@string/permlab_receiveMms"
        android:description="@string/permdesc_receiveMms" />

    <!-- Allows an application to receive emergency cell broadcast messages,
         to record or display them to the user. Reserved for system apps.
         @hide Pending API council approval -->
    <permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST"
        android:permissionGroup="android.permission-group.MESSAGES"
        android:protectionLevel="signatureOrSystem"
        android:label="@string/permlab_receiveEmergencyBroadcast"
        android:description="@string/permdesc_receiveEmergencyBroadcast" />

    <!-- Allows an application to read SMS messages. -->
    <permission android:name="android.permission.READ_SMS"
        android:permissionGroup="android.permission-group.MESSAGES"
+7 −0
Original line number Diff line number Diff line
@@ -427,6 +427,13 @@
      your messages or delete them without showing them to you.</string>

    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
    <string name="permlab_receiveEmergencyBroadcast">receive emergency broadcasts</string>
    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
    <string name="permdesc_receiveEmergencyBroadcast">Allows application to receive
      and process emergency broadcast messages. This permission is only available
      to system applications.</string>

     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
    <string name="permlab_sendSms">send SMS messages</string>
    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
    <string name="permdesc_sendSms">Allows application to send SMS
+114 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 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 android.telephony;

/**
 * Constants used in SMS Cell Broadcast messages.
 *
 * {@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;

    /** Bitmask for messages of ETWS type (including future extensions). */
    public static final int MESSAGE_ID_ETWS_TYPE_MASK       = 0xFFF8;

    /** Value for messages of ETWS type after applying {@link #MESSAGE_ID_ETWS_TYPE_MASK}. */
    public static final int MESSAGE_ID_ETWS_TYPE            = 0x1100;

    /** ETWS Message Identifier for earthquake warning message. */
    public static final int MESSAGE_ID_ETWS_EARTHQUAKE_WARNING      = 0x1100;

    /** ETWS Message Identifier for tsunami warning message. */
    public static final int MESSAGE_ID_ETWS_TSUNAMI_WARNING         = 0x1101;

    /** ETWS Message Identifier for earthquake and tsunami combined warning message. */
    public static final int MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING  = 0x1102;

    /** ETWS Message Identifier for test message. */
    public static final int MESSAGE_ID_ETWS_TEST_MESSAGE            = 0x1103;

    /** ETWS Message Identifier for messages related to other emergency types. */
    public static final int MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE    = 0x1104;

    /** Start of CMAS Message Identifier range. */
    public static final int MESSAGE_ID_CMAS_FIRST_IDENTIFIER                = 0x1112;

    /** CMAS Message Identifier for Presidential Level alerts. */
    public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL        = 0x1112;

    /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed. */
    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED = 0x1113;

    /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely. */
    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY  = 0x1114;

    /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed. */
    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED = 0x1115;

    /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely. */
    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY   = 0x1116;

    /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed. */
    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED = 0x1117;

    /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely. */
    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY   = 0x1118;

    /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed. */
    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED  = 0x1119;

    /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely. */
    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY    = 0x111A;

    /** CMAS Message Identifier for Child Abduction Emergency (Amber Alert). */
    public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY = 0x111B;

    /** CMAS Message Identifier for the Required Monthly Test. */
    public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST     = 0x111C;

    /** CMAS Message Identifier for CMAS Exercise. */
    public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE                  = 0x111D;

    /** CMAS Message Identifier for operator defined use. */
    public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE      = 0x111E;

    /** End of CMAS Message Identifier range (including future extensions). */
    public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER                 = 0x112F;

    /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
    public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER                  = 0x18FF;

    /** ETWS message code flag to activate the popup display. */
    public static final int MESSAGE_CODE_ETWS_ACTIVATE_POPUP                = 0x100;

    /** ETWS message code flag to activate the emergency user alert. */
    public static final int MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT          = 0x200;
}
+342 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 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 android.telephony;

import android.util.Log;

import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.gsm.SmsCbHeader;

import java.io.UnsupportedEncodingException;

/**
 * Describes an SMS-CB message.
 *
 * {@hide}
 */
public class SmsCbMessage {

    /**
     * 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;

    /**
     * Create an instance of this class from a received PDU
     *
     * @param pdu PDU bytes
     * @return An instance of this class, or null if invalid pdu
     */
    public static SmsCbMessage createFromPdu(byte[] pdu) {
        try {
            return new SmsCbMessage(pdu);
        } catch (IllegalArgumentException e) {
            Log.w(LOG_TAG, "Failed parsing SMS-CB pdu", e);
            return null;
        }
    }

    private static final String LOG_TAG = "SMSCB";

    /**
     * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
     */
    private static final String[] LANGUAGE_CODES_GROUP_0 = {
            "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu",
            "pl", null
    };

    /**
     * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
     */
    private static final String[] LANGUAGE_CODES_GROUP_2 = {
            "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null,
            null, null
    };

    private static final char CARRIAGE_RETURN = 0x0d;

    private static final int PDU_BODY_PAGE_LENGTH = 82;

    private SmsCbHeader mHeader;

    private String mLanguage;

    private String mBody;

    private SmsCbMessage(byte[] pdu) throws IllegalArgumentException {
        mHeader = new SmsCbHeader(pdu);
        parseBody(pdu);
    }

    /**
     * Return the geographical scope of this message, one of
     * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE},
     * {@link #GEOGRAPHICAL_SCOPE_PLMN_WIDE},
     * {@link #GEOGRAPHICAL_SCOPE_LA_WIDE},
     * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE}
     *
     * @return Geographical scope
     */
    public int getGeographicalScope() {
        return mHeader.geographicalScope;
    }

    /**
     * Get the ISO-639-1 language code for this message, or null if unspecified
     *
     * @return Language code
     */
    public String getLanguageCode() {
        return mLanguage;
    }

    /**
     * Get the body of this message, or null if no body available
     *
     * @return Body, or null
     */
    public String getMessageBody() {
        return mBody;
    }

    /**
     * Get the message identifier of this message (0-65535)
     *
     * @return Message identifier
     */
    public int getMessageIdentifier() {
        return mHeader.messageIdentifier;
    }

    /**
     * Get the message code of this message (0-1023)
     *
     * @return Message code
     */
    public int getMessageCode() {
        return mHeader.messageCode;
    }

    /**
     * Get the update number of this message (0-15)
     *
     * @return Update number
     */
    public int getUpdateNumber() {
        return mHeader.updateNumber;
    }

    /**
     * Parse and unpack the body text according to the encoding in the DCS.
     * After completing successfully this method will have assigned the body
     * text into mBody, and optionally the language code into mLanguage
     *
     * @param pdu The pdu
     */
    private void parseBody(byte[] pdu) {
        int encoding;
        boolean hasLanguageIndicator = false;

        // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
        // section 5.
        switch ((mHeader.dataCodingScheme & 0xf0) >> 4) {
            case 0x00:
                encoding = SmsMessage.ENCODING_7BIT;
                mLanguage = LANGUAGE_CODES_GROUP_0[mHeader.dataCodingScheme & 0x0f];
                break;

            case 0x01:
                hasLanguageIndicator = true;
                if ((mHeader.dataCodingScheme & 0x0f) == 0x01) {
                    encoding = SmsMessage.ENCODING_16BIT;
                } else {
                    encoding = SmsMessage.ENCODING_7BIT;
                }
                break;

            case 0x02:
                encoding = SmsMessage.ENCODING_7BIT;
                mLanguage = LANGUAGE_CODES_GROUP_2[mHeader.dataCodingScheme & 0x0f];
                break;

            case 0x03:
                encoding = SmsMessage.ENCODING_7BIT;
                break;

            case 0x04:
            case 0x05:
                switch ((mHeader.dataCodingScheme & 0x0c) >> 2) {
                    case 0x01:
                        encoding = SmsMessage.ENCODING_8BIT;
                        break;

                    case 0x02:
                        encoding = SmsMessage.ENCODING_16BIT;
                        break;

                    case 0x00:
                    default:
                        encoding = SmsMessage.ENCODING_7BIT;
                        break;
                }
                break;

            case 0x06:
            case 0x07:
                // Compression not supported
            case 0x09:
                // UDH structure not supported
            case 0x0e:
                // Defined by the WAP forum not supported
                encoding = SmsMessage.ENCODING_UNKNOWN;
                break;

            case 0x0f:
                if (((mHeader.dataCodingScheme & 0x04) >> 2) == 0x01) {
                    encoding = SmsMessage.ENCODING_8BIT;
                } else {
                    encoding = SmsMessage.ENCODING_7BIT;
                }
                break;

            default:
                // Reserved values are to be treated as 7-bit
                encoding = SmsMessage.ENCODING_7BIT;
                break;
        }

        if (mHeader.format == SmsCbHeader.FORMAT_UMTS) {
            // Payload may contain multiple pages
            int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];

            if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
                    * nrPages) {
                throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
                        + nrPages + " pages");
            }

            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < nrPages; i++) {
                // Each page is 82 bytes followed by a length octet indicating
                // the number of useful octets within those 82
                int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
                int length = pdu[offset + PDU_BODY_PAGE_LENGTH];

                if (length > PDU_BODY_PAGE_LENGTH) {
                    throw new IllegalArgumentException("Page length " + length
                            + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
                }

                sb.append(unpackBody(pdu, encoding, offset, length, hasLanguageIndicator));
            }
            mBody = sb.toString();
        } else {
            // Payload is one single page
            int offset = SmsCbHeader.PDU_HEADER_LENGTH;
            int length = pdu.length - offset;

            mBody = unpackBody(pdu, encoding, offset, length, hasLanguageIndicator);
        }
    }

    /**
     * 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
     * @return Body text
     */
    private String unpackBody(byte[] pdu, int encoding, int offset, int length,
            boolean hasLanguageIndicator) {
        String body = null;

        switch (encoding) {
            case SmsMessage.ENCODING_7BIT:
                body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);

                if (hasLanguageIndicator && body != null && body.length() > 2) {
                    // Language is two GSM characters followed by a CR.
                    // The actual body text is offset by 3 characters.
                    mLanguage = body.substring(0, 2);
                    body = body.substring(3);
                }
                break;

            case SmsMessage.ENCODING_16BIT:
                if (hasLanguageIndicator && pdu.length >= offset + 2) {
                    // Language is two GSM characters.
                    // The actual body text is offset by 2 bytes.
                    mLanguage = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
                    offset += 2;
                    length -= 2;
                }

                try {
                    body = new String(pdu, offset, (length & 0xfffe), "utf-16");
                } catch (UnsupportedEncodingException e) {
                    // Eeeek
                }
                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 body;
    }

    @Override
    public String toString() {
        return "SmsCbMessage{" + mHeader.toString() + ", language=" + mLanguage +
                ", body=\"" + mBody + "\"}";
    }
}
Loading