resolved conflicts for merge of ab79ee4a to gingerbread-plus-aosp

Change-Id: Ib885176060f65ef3286a24c7b9cae1a673666275
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 62f66b6..2e480e8 100755
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -542,7 +542,7 @@
              * 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>
              *
@@ -586,6 +586,46 @@
                     "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
              * not be saved.
@@ -618,7 +658,7 @@
              * @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][];
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c582eee..55cb6c8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -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"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ae3cd02..9a88ab3 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -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
diff --git a/telephony/java/android/telephony/SmsCbConstants.java b/telephony/java/android/telephony/SmsCbConstants.java
new file mode 100644
index 0000000..1ac9ae3
--- /dev/null
+++ b/telephony/java/android/telephony/SmsCbConstants.java
@@ -0,0 +1,114 @@
+/*
+ * 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;
+}
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
index 5608402..6da48d6 100644
--- a/telephony/java/android/telephony/SmsCbMessage.java
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -333,4 +333,10 @@
 
         return body;
     }
+
+    @Override
+    public String toString() {
+        return "SmsCbMessage{" + mHeader.toString() + ", language=" + mLanguage +
+                ", body=\"" + mBody + "\"}";
+    }
 }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 0ecd854..480186c 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -21,7 +21,6 @@
 import android.os.ServiceManager;
 import android.text.TextUtils;
 
-import com.android.internal.telephony.EncodeException;
 import com.android.internal.telephony.ISms;
 import com.android.internal.telephony.IccConstants;
 import com.android.internal.telephony.SmsRawData;
@@ -348,7 +347,7 @@
      * message identifier. Note that if two different clients enable the same
      * message identifier, they must both disable it for the device to stop
      * receiving those messages. All received messages will be broadcast in an
-     * intent with the action "android.provider.telephony.SMS_CB_RECEIVED".
+     * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
      * Note: This call is blocking, callers may want to avoid calling it from
      * the main thread of an application.
      *
@@ -404,6 +403,68 @@
     }
 
     /**
+     * Enable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range. Note that if two different clients enable the same
+     * message identifier, they must both disable it for the device to stop
+     * receiving those messages. All received messages will be broadcast in an
+     * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041
+     * @param endMessageId last message identifier as specified in TS 23.041
+     * @return true if successful, false otherwise
+     * @see #disableCellBroadcastRange(int, int)
+     *
+     * {@hide}
+     */
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
+        boolean success = false;
+
+        try {
+            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            if (iccISms != null) {
+                success = iccISms.enableCellBroadcastRange(startMessageId, endMessageId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
+     * Disable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range. Note that if two different clients enable the same
+     * message identifier, they must both disable it for the device to stop
+     * receiving those messages.
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041
+     * @param endMessageId last message identifier as specified in TS 23.041
+     * @return true if successful, false otherwise
+     *
+     * @see #enableCellBroadcastRange(int, int)
+     *
+     * {@hide}
+     */
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
+        boolean success = false;
+
+        try {
+            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            if (iccISms != null) {
+                success = iccISms.disableCellBroadcastRange(startMessageId, endMessageId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
      * Create a list of <code>SmsMessage</code>s from a list of RawSmsData
      * records returned by <code>getAllMessagesFromIcc()</code>
      *
diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
index 57d73e3..8ea1f8d 100644
--- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
@@ -83,6 +83,8 @@
      * GSM_EXTENDED_ESCAPE if this character is in the extended table.
      * In this case, you must call charToGsmExtended() for the value
      * that should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string.
+     * @param c the character to convert
+     * @return the GSM 7 bit table index for the specified character
      */
     public static int
     charToGsm(char c) {
@@ -96,12 +98,15 @@
 
     /**
      * Converts a char to a GSM 7 bit table index.
+     * Returns GSM_EXTENDED_ESCAPE if this character is in the extended table.
+     * In this case, you must call charToGsmExtended() for the value that
+     * should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string.
+     *
+     * @param c the character to convert
      * @param throwException If true, throws EncodeException on invalid char.
      *   If false, returns GSM alphabet ' ' char.
-     *
-     * Returns GSM_EXTENDED_ESCAPE if this character is in the extended table
-     * In this case, you must call charToGsmExtended() for the value that
-     * should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string
+     * @throws EncodeException encode error when throwException is true
+     * @return the GSM 7 bit table index for the specified character
      */
     public static int
     charToGsm(char c, boolean throwException) throws EncodeException {
@@ -130,6 +135,8 @@
      * Converts a char to an extended GSM 7 bit table index.
      * Extended chars should be escaped with GSM_EXTENDED_ESCAPE.
      * Returns ' ' in GSM alphabet if there's no possible match.
+     * @param c the character to convert
+     * @return the GSM 7 bit extended table index for the specified character
      */
     public static int
     charToGsmExtended(char c) {
@@ -152,6 +159,9 @@
      * gsmExtendedToChar().
      *
      * If an unmappable value is passed (one greater than 127), ' ' is returned.
+     *
+     * @param gsmChar the GSM 7 bit table index to convert
+     * @return the decoded character
      */
     public static char
     gsmToChar(int gsmChar) {
@@ -171,6 +181,9 @@
      *
      * If an unmappable value is passed, the character from the GSM 7 bit
      * default table will be used (table 6.2.1.1 of TS 23.038).
+     *
+     * @param gsmChar the GSM 7 bit extended table index to convert
+     * @return the decoded character
      */
     public static char
     gsmExtendedToChar(int gsmChar) {
@@ -241,6 +254,26 @@
      * septets.
      *
      * @param data the data string to encode
+     * @return the encoded string
+     * @throws EncodeException if String is too large to encode
+     */
+    public static byte[] stringToGsm7BitPacked(String data)
+            throws EncodeException {
+        return stringToGsm7BitPacked(data, 0, true, 0, 0);
+    }
+
+    /**
+     * Converts a String into a byte array containing
+     * the 7-bit packed GSM Alphabet representation of the string.
+     *
+     * Unencodable chars are encoded as spaces
+     *
+     * Byte 0 in the returned byte array is the count of septets used
+     * The returned byte array is the minimum size required to store
+     * the packed septets. The returned array cannot contain more than 255
+     * septets.
+     *
+     * @param data the data string to encode
      * @param languageTable the 7 bit language table, or 0 for the default GSM alphabet
      * @param languageShiftTable the 7 bit single shift language table, or 0 for the default
      *     GSM extension table
@@ -446,6 +479,11 @@
      *
      * Field may be padded with trailing 0xff's. The decode stops
      * at the first 0xff encountered.
+     *
+     * @param data the byte array to decode
+     * @param offset array offset for the first character to decode
+     * @param length the number of bytes to decode
+     * @return the decoded string
      */
     public static String
     gsm8BitUnpackedToString(byte[] data, int offset, int length) {
@@ -494,6 +532,8 @@
     /**
      * Convert a string into an 8-bit unpacked GSM alphabet byte array.
      * Always uses GSM default 7-bit alphabet and extension table.
+     * @param s the string to encode
+     * @return the 8-bit GSM encoded byte array for the string
      */
     public static byte[]
     stringToGsm8BitPacked(String s) {
@@ -512,11 +552,13 @@
 
     /**
      * Write a String into a GSM 8-bit unpacked field of
-     * @param length size at @param offset in @param dest
-     *
      * Field is padded with 0xff's, string is truncated if necessary
+     *
+     * @param s the string to encode
+     * @param dest the destination byte array
+     * @param offset the starting offset for the encoded string
+     * @param length the maximum number of bytes to write
      */
-
     public static void
     stringToGsm8BitUnpackedField(String s, byte dest[], int offset, int length) {
         int outByteIndex = offset;
@@ -558,6 +600,8 @@
     /**
      * Returns the count of 7-bit GSM alphabet characters
      * needed to represent this character. Counts unencodable char as 1 septet.
+     * @param c the character to examine
+     * @return the number of septets for this character
      */
     public static int
     countGsmSeptets(char c) {
@@ -572,8 +616,11 @@
     /**
      * Returns the count of 7-bit GSM alphabet characters
      * needed to represent this character using the default 7 bit GSM alphabet.
+     * @param c the character to examine
      * @param throwsException If true, throws EncodeException if unencodable
-     * char. Otherwise, counts invalid char as 1 septet
+     * char. Otherwise, counts invalid char as 1 septet.
+     * @return the number of septets for this character
+     * @throws EncodeException the character can't be encoded and throwsException is true
      */
     public static int
     countGsmSeptets(char c, boolean throwsException) throws EncodeException {
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 90de5e1..735f986 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -170,4 +170,32 @@
      */
     boolean disableCellBroadcast(int messageIdentifier);
 
+    /**
+     * Enable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range. Note that if two different clients enable
+     * a message identifier range, they must both disable it for the device
+     * to stop receiving those messages.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041
+     * @param endMessageId last message identifier as specified in TS 23.041
+     * @return true if successful, false otherwise
+     *
+     * @see #disableCellBroadcastRange(int, int)
+     */
+    boolean enableCellBroadcastRange(int startMessageId, int endMessageId);
+
+    /**
+     * Disable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range. Note that if two different clients enable
+     * a message identifier range, they must both disable it for the device
+     * to stop receiving those messages.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041
+     * @param endMessageId last message identifier as specified in TS 23.041
+     * @return true if successful, false otherwise
+     *
+     * @see #enableCellBroadcastRange(int, int)
+     */
+    boolean disableCellBroadcastRange(int startMessageId, int endMessageId);
+
 }
diff --git a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java
index 5049249..54de508 100644
--- a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java
+++ b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java
@@ -76,4 +76,13 @@
         return mIccSmsInterfaceManager.disableCellBroadcast(messageIdentifier);
     }
 
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId)
+            throws android.os.RemoteException {
+        return mIccSmsInterfaceManager.enableCellBroadcastRange(startMessageId, endMessageId);
+    }
+
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId)
+            throws android.os.RemoteException {
+        return mIccSmsInterfaceManager.disableCellBroadcastRange(startMessageId, endMessageId);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/IntRangeManager.java b/telephony/java/com/android/internal/telephony/IntRangeManager.java
new file mode 100644
index 0000000..889e2b1
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IntRangeManager.java
@@ -0,0 +1,560 @@
+/*
+ * 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 com.android.internal.telephony;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Clients can enable reception of SMS-CB messages for specific ranges of
+ * message identifiers (channels). This class keeps track of the currently
+ * enabled message identifiers and calls abstract methods to update the
+ * radio when the range of enabled message identifiers changes.
+ *
+ * An update is a call to {@link #startUpdate} followed by zero or more
+ * calls to {@link #addRange} followed by a call to {@link #finishUpdate}.
+ * Calls to {@link #enableRange} and {@link #disableRange} will perform
+ * an incremental update operation if the enabled ranges have changed.
+ * A full update operation (i.e. after a radio reset) can be performed
+ * by a call to {@link #updateRanges}.
+ *
+ * Clients are identified by String (the name associated with the User ID
+ * of the caller) so that a call to remove a range can be mapped to the
+ * client that enabled that range (or else rejected).
+ */
+public abstract class IntRangeManager {
+
+    /**
+     * Initial capacity for IntRange clients array list. There will be
+     * few cell broadcast listeners on a typical device, so this can be small.
+     */
+    private static final int INITIAL_CLIENTS_ARRAY_SIZE = 4;
+
+    /**
+     * One or more clients forming the continuous range [startId, endId].
+     * <p>When a client is added, the IntRange may merge with one or more
+     * adjacent IntRanges to form a single combined IntRange.
+     * <p>When a client is removed, the IntRange may divide into several
+     * non-contiguous IntRanges.
+     */
+    private class IntRange {
+        int startId;
+        int endId;
+        // sorted by earliest start id
+        final ArrayList<ClientRange> clients;
+
+        /**
+         * Create a new IntRange with a single client.
+         * @param startId the first id included in the range
+         * @param endId the last id included in the range
+         * @param client the client requesting the enabled range
+         */
+        IntRange(int startId, int endId, String client) {
+            this.startId = startId;
+            this.endId = endId;
+            clients = new ArrayList<ClientRange>(INITIAL_CLIENTS_ARRAY_SIZE);
+            clients.add(new ClientRange(startId, endId, client));
+        }
+
+        /**
+         * Create a new IntRange for an existing ClientRange.
+         * @param clientRange the initial ClientRange to add
+         */
+        IntRange(ClientRange clientRange) {
+            startId = clientRange.startId;
+            endId = clientRange.endId;
+            clients = new ArrayList<ClientRange>(INITIAL_CLIENTS_ARRAY_SIZE);
+            clients.add(clientRange);
+        }
+
+        /**
+         * Create a new IntRange from an existing IntRange. This is used for
+         * removing a ClientRange, because new IntRanges may need to be created
+         * for any gaps that open up after the ClientRange is removed. A copy
+         * is made of the elements of the original IntRange preceding the element
+         * that is being removed. The following elements will be added to this
+         * IntRange or to a new IntRange when a gap is found.
+         * @param intRange the original IntRange to copy elements from
+         * @param numElements the number of elements to copy from the original
+         */
+        IntRange(IntRange intRange, int numElements) {
+            this.startId = intRange.startId;
+            this.endId = intRange.endId;
+            this.clients = new ArrayList<ClientRange>(intRange.clients.size());
+            for (int i=0; i < numElements; i++) {
+                this.clients.add(intRange.clients.get(i));
+            }
+        }
+
+        /**
+         * Insert new ClientRange in order by start id.
+         * <p>If the new ClientRange is known to be sorted before or after the
+         * existing ClientRanges, or at a particular index, it can be added
+         * to the clients array list directly, instead of via this method.
+         * <p>Note that this can be changed from linear to binary search if the
+         * number of clients grows large enough that it would make a difference.
+         * @param range the new ClientRange to insert
+         */
+        void insert(ClientRange range) {
+            int len = clients.size();
+            for (int i=0; i < len; i++) {
+                ClientRange nextRange = clients.get(i);
+                if (range.startId <= nextRange.startId) {
+                    // ignore duplicate ranges from the same client
+                    if (!range.equals(nextRange)) {
+                        clients.add(i, range);
+                    }
+                    return;
+                }
+            }
+            clients.add(range);    // append to end of list
+        }
+    }
+
+    /**
+     * The message id range for a single client.
+     */
+    private class ClientRange {
+        final int startId;
+        final int endId;
+        final String client;
+
+        ClientRange(int startId, int endId, String client) {
+            this.startId = startId;
+            this.endId = endId;
+            this.client = client;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o != null && o instanceof ClientRange) {
+                ClientRange other = (ClientRange) o;
+                return startId == other.startId &&
+                        endId == other.endId &&
+                        client.equals(other.client);
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return (startId * 31 + endId) * 31 + client.hashCode();
+        }
+    }
+
+    /**
+     * List of integer ranges, one per client, sorted by start id.
+     */
+    private ArrayList<IntRange> mRanges = new ArrayList<IntRange>();
+
+    protected IntRangeManager() {}
+
+    /**
+     * Enable a range for the specified client and update ranges
+     * if necessary. If {@link #finishUpdate} returns failure,
+     * false is returned and the range is not added.
+     *
+     * @param startId the first id included in the range
+     * @param endId the last id included in the range
+     * @param client the client requesting the enabled range
+     * @return true if successful, false otherwise
+     */
+    public synchronized boolean enableRange(int startId, int endId, String client) {
+        int len = mRanges.size();
+
+        // empty range list: add the initial IntRange
+        if (len == 0) {
+            if (tryAddSingleRange(startId, endId, true)) {
+                mRanges.add(new IntRange(startId, endId, client));
+                return true;
+            } else {
+                return false;   // failed to update radio
+            }
+        }
+
+        for (int startIndex = 0; startIndex < len; startIndex++) {
+            IntRange range = mRanges.get(startIndex);
+            if (startId < range.startId) {
+                // test if new range completely precedes this range
+                // note that [1, 4] and [5, 6] coalesce to [1, 6]
+                if ((endId + 1) < range.startId) {
+                    // insert new int range before previous first range
+                    if (tryAddSingleRange(startId, endId, true)) {
+                        mRanges.add(startIndex, new IntRange(startId, endId, client));
+                        return true;
+                    } else {
+                        return false;   // failed to update radio
+                    }
+                } else if (endId <= range.endId) {
+                    // extend the start of this range
+                    if (tryAddSingleRange(startId, range.startId - 1, true)) {
+                        range.startId = startId;
+                        range.clients.add(0, new ClientRange(startId, endId, client));
+                        return true;
+                    } else {
+                        return false;   // failed to update radio
+                    }
+                } 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) {
+                            // try to add entire new range
+                            if (tryAddSingleRange(startId, endId, true)) {
+                                range.startId = startId;
+                                range.endId = endId;
+                                // insert new ClientRange before existing ranges
+                                range.clients.add(0, 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 start id to start of last overlapping range,
+                            // values from endRange.startId to endId are already enabled
+                            if (tryAddSingleRange(startId, endRange.startId - 1, true)) {
+                                range.startId = startId;
+                                range.endId = endRange.endId;
+                                // insert new ClientRange before existing ranges
+                                range.clients.add(0, new ClientRange(startId, endId, client));
+                                // coalesce range with following ranges up to endIndex
+                                // 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
+                            }
+                        }
+                    }
+
+                    // endId extends past all existing IntRanges: combine them all together
+                    if (tryAddSingleRange(startId, endId, true)) {
+                        range.startId = startId;
+                        range.endId = endId;
+                        // insert new ClientRange before existing ranges
+                        range.clients.add(0, new ClientRange(startId, endId, client));
+                        // coalesce range with following ranges up to len-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 < len; 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 ((startId + 1) <= range.endId) {
+                if (endId <= range.endId) {
+                    // completely contained in existing range; no radio changes
+                    range.insert(new ClientRange(startId, endId, client));
+                    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) {
+                            // 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;
+                                // insert new ClientRange in place
+                                range.insert(new ClientRange(startId, endId, client));
+                                // coalesce range with following ranges up to endIndex
+                                // 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
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // append new range after existing IntRanges
+        if (tryAddSingleRange(startId, endId, true)) {
+            mRanges.add(new IntRange(startId, endId, client));
+            return true;
+        } else {
+            return false;   // failed to update radio
+        }
+    }
+
+    /**
+     * Disable a range for the specified client and update ranges
+     * if necessary. If {@link #finishUpdate} returns failure,
+     * false is returned and the range is not removed.
+     *
+     * @param startId the first id included in the range
+     * @param endId the last id included in the range
+     * @param client the client requesting to disable the range
+     * @return true if successful, false otherwise
+     */
+    public synchronized boolean disableRange(int startId, int endId, String client) {
+        int len = mRanges.size();
+
+        for (int i=0; i < len; i++) {
+            IntRange range = mRanges.get(i);
+            if (startId < range.startId) {
+                return false;   // not found
+            } else if (endId <= range.endId) {
+                // found the IntRange that encloses the client range, if any
+                // search for it in the clients list
+                ArrayList<ClientRange> clients = range.clients;
+
+                // handle common case of IntRange containing one ClientRange
+                int crLength = clients.size();
+                if (crLength == 1) {
+                    ClientRange cr = clients.get(0);
+                    if (cr.startId == startId && cr.endId == endId && cr.client.equals(client)) {
+                        // disable range in radio then remove the entire IntRange
+                        if (tryAddSingleRange(startId, endId, false)) {
+                            mRanges.remove(i);
+                            return true;
+                        } else {
+                            return false;   // failed to update radio
+                        }
+                    } else {
+                        return false;   // not found
+                    }
+                }
+
+                // several ClientRanges: remove one, potentially splitting into many IntRanges.
+                // Save the original start and end id for the original IntRange
+                // in case the radio update fails and we have to revert it. If the
+                // update succeeds, we remove the client range and insert the new IntRanges.
+                int largestEndId = Integer.MIN_VALUE;  // largest end identifier found
+                boolean updateStarted = false;
+
+                for (int crIndex=0; crIndex < crLength; crIndex++) {
+                    ClientRange cr = clients.get(crIndex);
+                    if (cr.startId == startId && cr.endId == endId && cr.client.equals(client)) {
+                        // found the ClientRange to remove, check if it's the last in the list
+                        if (crIndex == crLength - 1) {
+                            if (range.endId == largestEndId) {
+                                // no channels to remove from radio; return success
+                                clients.remove(crIndex);
+                                return true;
+                            } else {
+                                // disable the channels at the end and lower the end id
+                                if (tryAddSingleRange(largestEndId + 1, range.endId, false)) {
+                                    clients.remove(crIndex);
+                                    range.endId = largestEndId;
+                                    return true;
+                                } else {
+                                    return false;
+                                }
+                            }
+                        }
+
+                        // copy the IntRange so that we can remove elements and modify the
+                        // start and end id's in the copy, leaving the original unmodified
+                        // until after the radio update succeeds
+                        IntRange rangeCopy = new IntRange(range, crIndex);
+
+                        if (crIndex == 0) {
+                            // removing the first ClientRange, so we may need to increase
+                            // the start id of the IntRange.
+                            // We know there are at least two ClientRanges in the list,
+                            // so clients.get(1) should always succeed.
+                            int nextStartId = clients.get(1).startId;
+                            if (nextStartId != range.startId) {
+                                startUpdate();
+                                updateStarted = true;
+                                addRange(range.startId, nextStartId - 1, false);
+                                rangeCopy.startId = nextStartId;
+                            }
+                        }
+
+                        // go through remaining ClientRanges, creating new IntRanges when
+                        // there is a gap in the sequence. After radio update succeeds,
+                        // 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++) {
+                            ClientRange nextCr = clients.get(nextIndex);
+                            if (nextCr.startId > largestEndId + 1) {
+                                if (!updateStarted) {
+                                    startUpdate();
+                                    updateStarted = true;
+                                }
+                                addRange(largestEndId + 1, nextCr.startId - 1, false);
+                                currentRange.endId = largestEndId;
+                                currentRange = new IntRange(nextCr);
+                            } else {
+                                currentRange.clients.add(nextCr);
+                            }
+                            if (nextCr.endId > largestEndId) {
+                                largestEndId = nextCr.endId;
+                            }
+                        }
+
+                        if (updateStarted) {
+                            if (!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 {
+                            return true;
+                        }
+                    } else {
+                        // not the ClientRange to remove; save highest end ID seen so far
+                        if (cr.endId > largestEndId) {
+                            largestEndId = cr.endId;
+                        }
+                    }
+                }
+            }
+        }
+
+        return false;   // not found
+    }
+
+    /**
+     * Perform a complete update operation (enable all ranges). Useful
+     * after a radio reset. Calls {@link #startUpdate}, followed by zero or
+     * more calls to {@link #addRange}, followed by {@link #finishUpdate}.
+     * @return true if successful, false otherwise
+     */
+    public boolean updateRanges() {
+        startUpdate();
+        Iterator<IntRange> iterator = mRanges.iterator();
+        if (iterator.hasNext()) {
+            IntRange range = iterator.next();
+            int start = range.startId;
+            int end = range.endId;
+            // accumulate ranges of [startId, endId]
+            while (iterator.hasNext()) {
+                IntRange nextNode = iterator.next();
+                // [startIdA, endIdA], [endIdA + 1, endIdB] -> [startIdA, endIdB]
+                if (nextNode.startId <= (end + 1)) {
+                    if (nextNode.endId > end) {
+                        end = nextNode.endId;
+                    }
+                } else {
+                    addRange(start, end, true);
+                    start = nextNode.startId;
+                    end = nextNode.endId;
+                }
+            }
+            // add final range
+            addRange(start, end, true);
+        }
+        return finishUpdate();
+    }
+
+    /**
+     * Enable or disable a single range of message identifiers.
+     * @param startId the first id included in the range
+     * @param endId the last id included in the range
+     * @param selected true to enable range, false to disable range
+     * @return true if successful, false otherwise
+     */
+    private boolean tryAddSingleRange(int startId, int endId, boolean selected) {
+        startUpdate();
+        addRange(startId, endId, selected);
+        return finishUpdate();
+    }
+
+    /**
+     * Called when the list of enabled ranges has changed. This will be
+     * followed by zero or more calls to {@link #addRange} followed by
+     * a call to {@link #finishUpdate}.
+     */
+    protected abstract void startUpdate();
+
+    /**
+     * Called after {@link #startUpdate} to indicate a range of enabled
+     * or disabled values.
+     *
+     * @param startId the first id included in the range
+     * @param endId the last id included in the range
+     * @param selected true to enable range, false to disable range
+     */
+    protected abstract void addRange(int startId, int endId, boolean selected);
+
+    /**
+     * Called to indicate the end of a range update started by the
+     * previous call to {@link #startUpdate}.
+     * @return true if successful, false otherwise
+     */
+    protected abstract boolean finishUpdate();
+}
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
old mode 100755
new mode 100644
index ad34550..69dd029
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -32,11 +32,9 @@
 import android.database.SQLException;
 import android.net.Uri;
 import android.os.AsyncResult;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
-import android.os.StatFs;
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
 import android.provider.Settings;
@@ -878,37 +876,6 @@
     protected abstract void sendMultipartSms (SmsTracker tracker);
 
     /**
-     * Activate or deactivate cell broadcast SMS.
-     *
-     * @param activate
-     *            0 = activate, 1 = deactivate
-     * @param response
-     *            Callback message is empty on completion
-     */
-    protected abstract void activateCellBroadcastSms(int activate, Message response);
-
-    /**
-     * Query the current configuration of cell broadcast SMS.
-     *
-     * @param response
-     *            Callback message contains the configuration from the modem on completion
-     *            @see #setCellBroadcastConfig
-     */
-    protected abstract void getCellBroadcastSmsConfig(Message response);
-
-    /**
-     * Configure cell broadcast SMS.
-     *
-     * @param configValuesArray
-     *          The first element defines the number of triples that follow.
-     *          A triple is made up of the service category, the language identifier
-     *          and a boolean that specifies whether the category is set active.
-     * @param response
-     *            Callback message is empty on completion
-     */
-    protected abstract void setCellBroadcastConfig(int[] configValuesArray, Message response);
-
-    /**
      * Send an acknowledge message.
      * @param success indicates that last message was successfully received.
      * @param result result code indicating any error
@@ -1014,14 +981,21 @@
 
     protected abstract void handleBroadcastSms(AsyncResult ar);
 
-    protected void dispatchBroadcastPdus(byte[][] pdus) {
-        Intent intent = new Intent("android.provider.telephony.SMS_CB_RECEIVED");
-        intent.putExtra("pdus", pdus);
+    protected void dispatchBroadcastPdus(byte[][] pdus, boolean isEmergencyMessage) {
+        if (isEmergencyMessage) {
+            Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
+            intent.putExtra("pdus", pdus);
+            if (Config.LOGD)
+                Log.d(TAG, "Dispatching " + pdus.length + " emergency SMS CB pdus");
 
-        if (Config.LOGD)
-            Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus");
+            dispatch(intent, "android.permission.RECEIVE_EMERGENCY_BROADCAST");
+        } else {
+            Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
+            intent.putExtra("pdus", pdus);
+            if (Config.LOGD)
+                Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus");
 
-        dispatch(intent, "android.permission.RECEIVE_SMS");
+            dispatch(intent, "android.permission.RECEIVE_SMS");
+        }
     }
-
 }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 3890a98..1efae21 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -1163,7 +1163,8 @@
      * @param response Callback message is empty on completion
      */
     public void activateCellBroadcastSms(int activate, Message response) {
-        mSMS.activateCellBroadcastSms(activate, response);
+        Log.e(LOG_TAG, "[CDMAPhone] activateCellBroadcastSms() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
     /**
@@ -1172,7 +1173,8 @@
      * @param response Callback message is empty on completion
      */
     public void getCellBroadcastSmsConfig(Message response) {
-        mSMS.getCellBroadcastSmsConfig(response);
+        Log.e(LOG_TAG, "[CDMAPhone] getCellBroadcastSmsConfig() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
     /**
@@ -1181,7 +1183,8 @@
      * @param response Callback message is empty on completion
      */
     public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
-        mSMS.setCellBroadcastConfig(configValuesArray, response);
+        Log.e(LOG_TAG, "[CDMAPhone] setCellBroadcastSmsConfig() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
     private static final String IS683A_FEATURE_CODE = "*228";
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index d6fc134..45b58e5 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -265,7 +265,7 @@
             if (cursorCount != totalSegments - 1) {
                 // We don't have all the parts yet, store this one away
                 ContentValues values = new ContentValues();
-                values.put("date", new Long(0));
+                values.put("date", (long) 0);
                 values.put("pdu", HexDump.toHexString(pdu, index, pdu.length - index));
                 values.put("address", address);
                 values.put("reference_number", referenceNumber);
@@ -468,21 +468,6 @@
         }
     }
 
-    /** {@inheritDoc} */
-    protected void activateCellBroadcastSms(int activate, Message response) {
-        mCm.setCdmaBroadcastActivation((activate == 0), response);
-    }
-
-    /** {@inheritDoc} */
-    protected void getCellBroadcastSmsConfig(Message response) {
-        mCm.getCdmaBroadcastConfig(response);
-    }
-
-    /** {@inheritDoc} */
-    protected void setCellBroadcastConfig(int[] configValuesArray, Message response) {
-        mCm.setCdmaBroadcastConfig(configValuesArray, response);
-    }
-
     protected void handleBroadcastSms(AsyncResult ar) {
         // Not supported
         Log.e(TAG, "Error! Not implemented for CDMA.");
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
index f4a6d11..7b42b06 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
@@ -204,6 +204,18 @@
         return false;
     }
 
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
+        // Not implemented
+        Log.e(LOG_TAG, "Error! Not implemented for CDMA.");
+        return false;
+    }
+
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
+        // Not implemented
+        Log.e(LOG_TAG, "Error! Not implemented for CDMA.");
+        return false;
+    }
+
     protected void log(String msg) {
         Log.d(LOG_TAG, "[RuimSmsInterfaceManager] " + msg);
     }
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index 3959c67..c5be856 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -1471,16 +1471,35 @@
         return this.mIccFileHandler;
     }
 
+    /**
+     * Activate or deactivate cell broadcast SMS.
+     *
+     * @param activate 0 = activate, 1 = deactivate
+     * @param response Callback message is empty on completion
+     */
     public void activateCellBroadcastSms(int activate, Message response) {
-        Log.e(LOG_TAG, "Error! This functionality is not implemented for GSM.");
+        Log.e(LOG_TAG, "[GSMPhone] activateCellBroadcastSms() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
+    /**
+     * Query the current configuration of cdma cell broadcast SMS.
+     *
+     * @param response Callback message is empty on completion
+     */
     public void getCellBroadcastSmsConfig(Message response) {
-        Log.e(LOG_TAG, "Error! This functionality is not implemented for GSM.");
+        Log.e(LOG_TAG, "[GSMPhone] getCellBroadcastSmsConfig() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
-    public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response){
-        Log.e(LOG_TAG, "Error! This functionality is not implemented for GSM.");
+    /**
+     * Configure cdma cell broadcast SMS.
+     *
+     * @param response Callback message is empty on completion
+     */
+    public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
+        Log.e(LOG_TAG, "[GSMPhone] setCellBroadcastSmsConfig() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
     public boolean isCspPlmnEnabled() {
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
old mode 100755
new mode 100644
index 14ad59a..de769d1
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -350,6 +350,7 @@
      *
      * @param tracker holds the multipart Sms tracker ready to be sent
      */
+    @Override
     protected void sendMultipartSms (SmsTracker tracker) {
         ArrayList<String> parts;
         ArrayList<PendingIntent> sentIntents;
@@ -370,6 +371,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
         // FIXME unit test leaves cm == null. this should change
         if (mCm != null) {
@@ -377,27 +379,6 @@
         }
     }
 
-    /** {@inheritDoc} */
-    protected void activateCellBroadcastSms(int activate, Message response) {
-        // Unless CBS is implemented for GSM, this point should be unreachable.
-        Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
-        response.recycle();
-    }
-
-    /** {@inheritDoc} */
-    protected void getCellBroadcastSmsConfig(Message response){
-        // Unless CBS is implemented for GSM, this point should be unreachable.
-        Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
-        response.recycle();
-    }
-
-    /** {@inheritDoc} */
-    protected  void setCellBroadcastConfig(int[] configValuesArray, Message response) {
-        // Unless CBS is implemented for GSM, this point should be unreachable.
-        Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
-        response.recycle();
-    }
-
     private int resultToCause(int rc) {
         switch (rc) {
             case Activity.RESULT_OK:
@@ -489,9 +470,10 @@
     }
 
     // This map holds incomplete concatenated messages waiting for assembly
-    private HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
+    private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
             new HashMap<SmsCbConcatInfo, byte[][]>();
 
+    @Override
     protected void handleBroadcastSms(AsyncResult ar) {
         try {
             byte[][] pdus = null;
@@ -503,9 +485,9 @@
                     for (int j = i; j < i + 8 && j < receivedPdu.length; j++) {
                         int b = receivedPdu[j] & 0xff;
                         if (b < 0x10) {
-                            sb.append("0");
+                            sb.append('0');
                         }
-                        sb.append(Integer.toHexString(b)).append(" ");
+                        sb.append(Integer.toHexString(b)).append(' ');
                     }
                     Log.d(TAG, sb.toString());
                 }
@@ -550,7 +532,8 @@
                 pdus[0] = receivedPdu;
             }
 
-            dispatchBroadcastPdus(pdus);
+            boolean isEmergencyMessage = SmsCbHeader.isEmergencyMessage(header.messageIdentifier);
+            dispatchBroadcastPdus(pdus, isEmergencyMessage);
 
             // Remove messages that are out of scope to prevent the map from
             // growing indefinitely, containing incomplete messages that were
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
index e55596b..6e87f21 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -27,6 +27,7 @@
 import com.android.internal.telephony.IccConstants;
 import com.android.internal.telephony.IccSmsInterfaceManager;
 import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.IntRangeManager;
 import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsRawData;
 
@@ -53,6 +54,9 @@
     private HashMap<Integer, HashSet<String>> mCellBroadcastSubscriptions =
             new HashMap<Integer, HashSet<String>>();
 
+    private CellBroadcastRangeManager mCellBroadcastRangeManager =
+            new CellBroadcastRangeManager();
+
     private static final int EVENT_LOAD_DONE = 1;
     private static final int EVENT_UPDATE_DONE = 2;
     private static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3;
@@ -212,7 +216,15 @@
     }
 
     public boolean enableCellBroadcast(int messageIdentifier) {
-        if (DBG) log("enableCellBroadcast");
+        return enableCellBroadcastRange(messageIdentifier, messageIdentifier);
+    }
+
+    public boolean disableCellBroadcast(int messageIdentifier) {
+        return disableCellBroadcastRange(messageIdentifier, messageIdentifier);
+    }
+
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
+        if (DBG) log("enableCellBroadcastRange");
 
         Context context = mPhone.getContext();
 
@@ -222,30 +234,22 @@
 
         String client = context.getPackageManager().getNameForUid(
                 Binder.getCallingUid());
-        HashSet<String> clients = mCellBroadcastSubscriptions.get(messageIdentifier);
 
-        if (clients == null) {
-            // This is a new message identifier
-            clients = new HashSet<String>();
-            mCellBroadcastSubscriptions.put(messageIdentifier, clients);
-
-            if (!updateCellBroadcastConfig()) {
-                mCellBroadcastSubscriptions.remove(messageIdentifier);
-                return false;
-            }
+        if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
+            log("Failed to add cell broadcast subscription for MID range " + startMessageId
+                    + " to " + endMessageId + " from client " + client);
+            return false;
         }
 
-        clients.add(client);
-
         if (DBG)
-            log("Added cell broadcast subscription for MID " + messageIdentifier
-                    + " from client " + client);
+            log("Added cell broadcast subscription for MID range " + startMessageId
+                    + " to " + endMessageId + " from client " + client);
 
         return true;
     }
 
-    public boolean disableCellBroadcast(int messageIdentifier) {
-        if (DBG) log("disableCellBroadcast");
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
+        if (DBG) log("disableCellBroadcastRange");
 
         Context context = mPhone.getContext();
 
@@ -255,39 +259,56 @@
 
         String client = context.getPackageManager().getNameForUid(
                 Binder.getCallingUid());
-        HashSet<String> clients = mCellBroadcastSubscriptions.get(messageIdentifier);
 
-        if (clients != null && clients.remove(client)) {
-            if (DBG)
-                log("Removed cell broadcast subscription for MID " + messageIdentifier
-                        + " from client " + client);
-
-            if (clients.isEmpty()) {
-                mCellBroadcastSubscriptions.remove(messageIdentifier);
-                updateCellBroadcastConfig();
-            }
-            return true;
+        if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
+            log("Failed to remove cell broadcast subscription for MID range " + startMessageId
+                    + " to " + endMessageId + " from client " + client);
+            return false;
         }
 
-        return false;
+        if (DBG)
+            log("Removed cell broadcast subscription for MID range " + startMessageId
+                    + " to " + endMessageId + " from client " + client);
+
+        return true;
     }
 
-    private boolean updateCellBroadcastConfig() {
-        Set<Integer> messageIdentifiers = mCellBroadcastSubscriptions.keySet();
+    class CellBroadcastRangeManager extends IntRangeManager {
+        private ArrayList<SmsBroadcastConfigInfo> mConfigList =
+                new ArrayList<SmsBroadcastConfigInfo>();
 
-        if (messageIdentifiers.size() > 0) {
-            SmsBroadcastConfigInfo[] configs =
-                    new SmsBroadcastConfigInfo[messageIdentifiers.size()];
-            int i = 0;
+        /**
+         * Called when the list of enabled ranges has changed. This will be
+         * followed by zero or more calls to {@link #addRange} followed by
+         * a call to {@link #finishUpdate}.
+         */
+        protected void startUpdate() {
+            mConfigList.clear();
+        }
 
-            for (int messageIdentifier : messageIdentifiers) {
-                configs[i++] = new SmsBroadcastConfigInfo(messageIdentifier, messageIdentifier,
-                        SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, true);
+        /**
+         * Called after {@link #startUpdate} to indicate a range of enabled
+         * values.
+         * @param startId the first id included in the range
+         * @param endId the last id included in the range
+         */
+        protected void addRange(int startId, int endId, boolean selected) {
+            mConfigList.add(new SmsBroadcastConfigInfo(startId, endId,
+                        SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected));
+        }
+
+        /**
+         * Called to indicate the end of a range update started by the
+         * previous call to {@link #startUpdate}.
+         */
+        protected boolean finishUpdate() {
+            if (mConfigList.isEmpty()) {
+                return setCellBroadcastActivation(false);
+            } else {
+                SmsBroadcastConfigInfo[] configs =
+                        mConfigList.toArray(new SmsBroadcastConfigInfo[mConfigList.size()]);
+                return setCellBroadcastConfig(configs) && setCellBroadcastActivation(true);
             }
-
-            return setCellBroadcastConfig(configs) && setCellBroadcastActivation(true);
-        } else {
-            return setCellBroadcastActivation(false);
         }
     }
 
@@ -313,7 +334,7 @@
 
     private boolean setCellBroadcastActivation(boolean activate) {
         if (DBG)
-            log("Calling setCellBroadcastActivation(" + activate + ")");
+            log("Calling setCellBroadcastActivation(" + activate + ')');
 
         synchronized (mLock) {
             Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE);
@@ -331,6 +352,7 @@
         return mSuccess;
     }
 
+    @Override
     protected void log(String msg) {
         Log.d(LOG_TAG, "[SimSmsInterfaceManager] " + msg);
     }
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
index 45f50bc..66e7ce0 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
@@ -30,11 +30,11 @@
  * and 9.4.4.2.3 for UMTS.
  * All other values can be treated as empty CBM data coding scheme.
  *
- * selected false means message types specified in <fromServiceId, toServiceId>
- * and <fromCodeScheme, toCodeScheme>are not accepted, while true means accepted.
+ * selected false means message types specified in {@code <fromServiceId, toServiceId>}
+ * and {@code <fromCodeScheme, toCodeScheme>} are not accepted, while true means accepted.
  *
  */
-public class SmsBroadcastConfigInfo {
+public final class SmsBroadcastConfigInfo {
     private int fromServiceId;
     private int toServiceId;
     private int fromCodeScheme;
@@ -46,11 +46,11 @@
      */
     public SmsBroadcastConfigInfo(int fromId, int toId, int fromScheme,
             int toScheme, boolean selected) {
-        setFromServiceId(fromId);
-        setToServiceId(toId);
-        setFromCodeScheme(fromScheme);
-        setToCodeScheme(toScheme);
-        this.setSelected(selected);
+        fromServiceId = fromId;
+        toServiceId = toId;
+        fromCodeScheme = fromScheme;
+        toCodeScheme = toScheme;
+        this.selected = selected;
     }
 
     /**
@@ -126,8 +126,8 @@
     @Override
     public String toString() {
         return "SmsBroadcastConfigInfo: Id [" +
-            getFromServiceId() + "," + getToServiceId() + "] Code [" +
-            getFromCodeScheme() + "," + getToCodeScheme() + "] " +
-            (isSelected() ? "ENABLED" : "DISABLED");
+                fromServiceId + ',' + toServiceId + "] Code [" +
+                fromCodeScheme + ',' + toCodeScheme + "] " +
+            (selected ? "ENABLED" : "DISABLED");
     }
-}
\ No newline at end of file
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
index 0945a38..ea4d684 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
@@ -16,7 +16,9 @@
 
 package com.android.internal.telephony.gsm;
 
-public class SmsCbHeader {
+import android.telephony.SmsCbConstants;
+
+public class SmsCbHeader implements SmsCbConstants {
     /**
      * Length of SMS-CB header
      */
@@ -107,4 +109,72 @@
             nrOfPages = 1;
         }
     }
+
+    /**
+     * Return whether the specified message ID is an emergency (PWS) message type.
+     * This method is static and takes an argument so that it can be used by
+     * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
+     * @param id the message identifier to check
+     * @return true if the message is emergency type; false otherwise
+     */
+    public static boolean isEmergencyMessage(int id) {
+        return id >= MESSAGE_ID_PWS_FIRST_IDENTIFIER && id <= MESSAGE_ID_PWS_LAST_IDENTIFIER;
+    }
+
+    /**
+     * Return whether the specified message ID is an ETWS emergency message type.
+     * This method is static and takes an argument so that it can be used by
+     * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
+     * @param id the message identifier to check
+     * @return true if the message is ETWS emergency type; false otherwise
+     */
+    public static boolean isEtwsMessage(int id) {
+        return (id & MESSAGE_ID_ETWS_TYPE_MASK) == MESSAGE_ID_ETWS_TYPE;
+    }
+
+    /**
+     * Return whether the specified message ID is a CMAS emergency message type.
+     * This method is static and takes an argument so that it can be used by
+     * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
+     * @param id the message identifier to check
+     * @return true if the message is CMAS emergency type; false otherwise
+     */
+    public static boolean isCmasMessage(int id) {
+        return id >= MESSAGE_ID_CMAS_FIRST_IDENTIFIER && id <= MESSAGE_ID_CMAS_LAST_IDENTIFIER;
+    }
+
+    /**
+     * Return whether the specified message code indicates an ETWS popup alert.
+     * This method is static and takes an argument so that it can be used by
+     * CellBroadcastReceiver, which stores message codes in SQLite rather than PDU.
+     * This method assumes that the message ID has already been checked for ETWS type.
+     *
+     * @param messageCode the message code to check
+     * @return true if the message code indicates a popup alert should be displayed
+     */
+    public static boolean isEtwsPopupAlert(int messageCode) {
+        return (messageCode & MESSAGE_CODE_ETWS_ACTIVATE_POPUP) != 0;
+    }
+
+    /**
+     * Return whether the specified message code indicates an ETWS emergency user alert.
+     * This method is static and takes an argument so that it can be used by
+     * CellBroadcastReceiver, which stores message codes in SQLite rather than PDU.
+     * This method assumes that the message ID has already been checked for ETWS type.
+     *
+     * @param messageCode the message code to check
+     * @return true if the message code indicates an emergency user alert
+     */
+    public static boolean isEtwsEmergencyUserAlert(int messageCode) {
+        return (messageCode & MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT) != 0;
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbHeader{GS=" + geographicalScope + ", messageCode=0x" +
+                Integer.toHexString(messageCode) + ", updateNumber=" + updateNumber +
+                ", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) +
+                ", DCS=0x" + Integer.toHexString(dataCodingScheme) +
+                ", page " + pageIndex + " of " + nrOfPages + '}';
+    }
 }