diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 91a72a50c23a3..6585e8201d9a5 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -547,7 +547,7 @@ public final class Telephony { * values:

* * * @@ -590,6 +590,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:

+ * + * + * + *

The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.

+ * + *

If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.

+ */ + @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:

+ * + * + * + *

The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.

+ * + *

If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.

+ */ + @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 @@ -623,7 +663,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][]; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 3efb83c04b401..838e34e422617 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -159,6 +159,15 @@ android:label="@string/permlab_receiveMms" android:description="@string/permdesc_receiveMms" /> + + + + receive emergency broadcasts + + Allows application to receive + and process emergency broadcast messages. This permission is only available + to system applications. + + send SMS messages 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 0000000000000..1ac9ae3fa0162 --- /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 5608402b5f3eb..6da48d6864f80 100644 --- a/telephony/java/android/telephony/SmsCbMessage.java +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -333,4 +333,10 @@ public class SmsCbMessage { 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 6a346af37bd50..5bdc146792f35 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -345,7 +345,7 @@ public final class SmsManager { * 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. * @@ -400,6 +400,68 @@ public final class SmsManager { return success; } + /** + * 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 SmsMessages from a list of RawSmsData * records returned by getAllMessagesFromIcc() diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java index c7c91e4d9e993..a3f4f1fe2f22d 100644 --- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java @@ -86,6 +86,8 @@ public class GsmAlphabet { * 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) { @@ -99,12 +101,15 @@ public class GsmAlphabet { /** * 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 { @@ -133,6 +138,8 @@ public class GsmAlphabet { * 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) { @@ -155,6 +162,9 @@ public class GsmAlphabet { * 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) { @@ -174,6 +184,9 @@ public class GsmAlphabet { * * 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) { @@ -232,6 +245,26 @@ public class GsmAlphabet { return ret; } + /** + * 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 + * @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. @@ -449,6 +482,11 @@ public class GsmAlphabet { * * 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) { @@ -532,6 +570,8 @@ public class GsmAlphabet { /** * 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) { @@ -550,11 +590,13 @@ public class GsmAlphabet { /** * 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; @@ -596,6 +638,8 @@ public class GsmAlphabet { /** * 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) { @@ -610,8 +654,11 @@ public class GsmAlphabet { /** * 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 90de5e1c9a7d6..735f986917bc1 100644 --- a/telephony/java/com/android/internal/telephony/ISms.aidl +++ b/telephony/java/com/android/internal/telephony/ISms.aidl @@ -170,4 +170,32 @@ interface ISms { */ 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 5049249053ba8..54de508c04dee 100644 --- a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java +++ b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java @@ -76,4 +76,13 @@ public class IccSmsInterfaceManagerProxy extends ISms.Stub { 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 0000000000000..889e2b1cde36d --- /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]. + *

When a client is added, the IntRange may merge with one or more + * adjacent IntRanges to form a single combined IntRange. + *

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 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(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(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(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. + *

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. + *

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 mRanges = new ArrayList(); + + 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 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 newRanges = new ArrayList(); + 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 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 8efc9aa4bd25f..76e719c3c8954 --- 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.Cursor; 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.os.SystemProperties; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; @@ -907,37 +905,6 @@ public abstract class SMSDispatcher extends Handler { */ 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 - */ - public 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 - */ - public 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 - */ - public abstract void setCellBroadcastConfig(int[] configValuesArray, Message response); - /** * Send an acknowledge message. * @param success indicates that last message was successfully received. @@ -1065,14 +1032,17 @@ public abstract class SMSDispatcher extends Handler { 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); - - if (false) + protected void dispatchBroadcastPdus(byte[][] pdus, boolean isEmergencyMessage) { + if (isEmergencyMessage) { + Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION); + intent.putExtra("pdus", pdus); + Log.d(TAG, "Dispatching " + pdus.length + " emergency SMS CB pdus"); + dispatch(intent, "android.permission.RECEIVE_EMERGENCY_BROADCAST"); + } else { + Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION); + intent.putExtra("pdus", pdus); 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 a283062cc674c..bac15a6901d3b 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java @@ -1121,7 +1121,8 @@ public class CDMAPhone extends PhoneBase { * @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(); } /** @@ -1130,7 +1131,8 @@ public class CDMAPhone extends PhoneBase { * @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(); } /** @@ -1139,7 +1141,8 @@ public class CDMAPhone extends PhoneBase { * @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(); } /** diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java index cf6b78ab020de..29349db646d81 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java @@ -273,7 +273,7 @@ final class CdmaSMSDispatcher extends SMSDispatcher { 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); @@ -482,24 +482,6 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } } - /** {@inheritDoc} */ - @Override - public void activateCellBroadcastSms(int activate, Message response) { - mCm.setCdmaBroadcastActivation((activate == 0), response); - } - - /** {@inheritDoc} */ - @Override - public void getCellBroadcastSmsConfig(Message response) { - mCm.getCdmaBroadcastConfig(response); - } - - /** {@inheritDoc} */ - @Override - public 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 29f3bc1942a8a..9cd059d3e3973 100644 --- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java @@ -203,6 +203,18 @@ public class RuimSmsInterfaceManager extends IccSmsInterfaceManager { 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 3ccc03d680b8a..48debd403a700 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java +++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java @@ -1427,16 +1427,35 @@ public class GSMPhone extends PhoneBase { 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 b021967c61129..52ca453047140 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java @@ -391,30 +391,6 @@ final class GsmSMSDispatcher extends SMSDispatcher { } } - /** {@inheritDoc} */ - @Override - public 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} */ - @Override - public 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} */ - @Override - public 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: @@ -506,9 +482,10 @@ final class GsmSMSDispatcher extends SMSDispatcher { } // This map holds incomplete concatenated messages waiting for assembly - private HashMap mSmsCbPageMap = + private final HashMap mSmsCbPageMap = new HashMap(); + @Override protected void handleBroadcastSms(AsyncResult ar) { try { byte[][] pdus = null; @@ -520,9 +497,9 @@ final class GsmSMSDispatcher extends SMSDispatcher { 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()); } @@ -567,7 +544,8 @@ final class GsmSMSDispatcher extends SMSDispatcher { 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 5abba6e9fe8ca..8d0e5d3eefda6 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 android.util.Log; 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 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { private HashMap> mCellBroadcastSubscriptions = new HashMap>(); + 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; @@ -213,7 +217,15 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { } 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(); @@ -223,30 +235,22 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { String client = context.getPackageManager().getNameForUid( Binder.getCallingUid()); - HashSet clients = mCellBroadcastSubscriptions.get(messageIdentifier); - if (clients == null) { - // This is a new message identifier - clients = new HashSet(); - 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(); @@ -256,39 +260,56 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { String client = context.getPackageManager().getNameForUid( Binder.getCallingUid()); - HashSet 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 messageIdentifiers = mCellBroadcastSubscriptions.keySet(); + class CellBroadcastRangeManager extends IntRangeManager { + private ArrayList mConfigList = + new ArrayList(); - 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); } } @@ -314,7 +335,7 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { 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); diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java index 45f50bc49fb18..66e7ce0783cfd 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 @@ package com.android.internal.telephony.gsm; * 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 - * and are not accepted, while true means accepted. + * selected false means message types specified in {@code } + * and {@code } 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 class SmsBroadcastConfigInfo { */ 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 @@ public class SmsBroadcastConfigInfo { @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 0945a3812530e..ea4d68417e401 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 @@ public class SmsCbHeader { 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 + '}'; + } }