diff options
20 files changed, 578 insertions, 2898 deletions
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 9612151..0e6d07d 100755 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -596,8 +596,8 @@ public final class Telephony { * values:</p> * * <ul> - * <li><em>message</em> - An SmsCbMessage object containing the broadcast message - * data. This is not an emergency alert, so ETWS and CMAS data will be null.</li> + * <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 @@ -616,8 +616,8 @@ public final class Telephony { * values:</p> * * <ul> - * <li><em>message</em> - An SmsCbMessage object containing the broadcast message - * data, including ETWS or CMAS warning notification info if present.</li> + * <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 @@ -631,26 +631,6 @@ public final class Telephony { "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED"; /** - * Broadcast Action: A new CDMA SMS has been received containing Service Category - * Program Data (updates the list of enabled broadcast channels). The intent will - * have the following extra values:</p> - * - * <ul> - * <li><em>operations</em> - An array of CdmaSmsCbProgramData objects containing - * the service category operations (add/delete/clear) to perform.</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_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION = - "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_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. diff --git a/core/java/com/android/internal/util/BitwiseOutputStream.java b/core/java/com/android/internal/util/BitwiseOutputStream.java index ddecbed..70c0be8 100644 --- a/core/java/com/android/internal/util/BitwiseOutputStream.java +++ b/core/java/com/android/internal/util/BitwiseOutputStream.java @@ -77,7 +77,6 @@ public class BitwiseOutputStream { byte[] newBuf = new byte[(mPos + bits) >>> 2]; System.arraycopy(mBuf, 0, newBuf, 0, mEnd >>> 3); mBuf = newBuf; - mEnd = newBuf.length << 3; } /** diff --git a/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java b/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java index 306f58f..a304b68 100644 --- a/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java @@ -133,25 +133,4 @@ public class BitwiseStreamsTest extends AndroidTestCase { long end = android.os.SystemClock.elapsedRealtime(); Log.d(LOG_TAG, "repeated encode-decode took " + (end - start) + " ms"); } - - @SmallTest - public void testExpandArray() throws Exception { - Random random = new Random(); - int iterations = 10000; - int[] sizeArr = new int[iterations]; - int[] valueArr = new int[iterations]; - BitwiseOutputStream outStream = new BitwiseOutputStream(8); - for (int i = 0; i < iterations; i++) { - int x = random.nextInt(); - int size = (x & 0x07) + 1; - int value = x & (-1 >>> (32 - size)); - sizeArr[i] = size; - valueArr[i] = value; - outStream.write(size, value); - } - BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); - for (int i = 0; i < iterations; i++) { - assertEquals(valueArr[i], inStream.read(sizeArr[i])); - } - } } diff --git a/telephony/java/android/telephony/SmsCbCmasInfo.java b/telephony/java/android/telephony/SmsCbCmasInfo.java deleted file mode 100644 index 7a89d94..0000000 --- a/telephony/java/android/telephony/SmsCbCmasInfo.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.telephony; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}. - * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and - * 3GPP TS 23.041 (for GSM/UMTS). - * - * {@hide} - */ -public class SmsCbCmasInfo implements Parcelable { - - // CMAS message class (in GSM/UMTS message identifier or CDMA service category). - - /** Presidential-level alert (Korean Public Alert System Class 0 message). */ - public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0x00; - - /** Extreme threat to life and property (Korean Public Alert System Class 1 message). */ - public static final int CMAS_CLASS_EXTREME_THREAT = 0x01; - - /** Severe threat to life and property (Korean Public Alert System Class 1 message). */ - public static final int CMAS_CLASS_SEVERE_THREAT = 0x02; - - /** Child abduction emergency (AMBER Alert). */ - public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 0x03; - - /** CMAS test message. */ - public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 0x04; - - /** CMAS exercise. */ - public static final int CMAS_CLASS_CMAS_EXERCISE = 0x05; - - /** CMAS category for operator defined use. */ - public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 0x06; - - /** CMAS category for warning types that are reserved for future extension. */ - public static final int CMAS_CLASS_UNKNOWN = -1; - - // CMAS alert category (in CDMA type 1 elements record). - - /** CMAS alert category: Geophysical including landslide. */ - public static final int CMAS_CATEGORY_GEO = 0x00; - - /** CMAS alert category: Meteorological including flood. */ - public static final int CMAS_CATEGORY_MET = 0x01; - - /** CMAS alert category: General emergency and public safety. */ - public static final int CMAS_CATEGORY_SAFETY = 0x02; - - /** CMAS alert category: Law enforcement, military, homeland/local/private security. */ - public static final int CMAS_CATEGORY_SECURITY = 0x03; - - /** CMAS alert category: Rescue and recovery. */ - public static final int CMAS_CATEGORY_RESCUE = 0x04; - - /** CMAS alert category: Fire suppression and rescue. */ - public static final int CMAS_CATEGORY_FIRE = 0x05; - - /** CMAS alert category: Medical and public health. */ - public static final int CMAS_CATEGORY_HEALTH = 0x06; - - /** CMAS alert category: Pollution and other environmental. */ - public static final int CMAS_CATEGORY_ENV = 0x07; - - /** CMAS alert category: Public and private transportation. */ - public static final int CMAS_CATEGORY_TRANSPORT = 0x08; - - /** CMAS alert category: Utility, telecom, other non-transport infrastructure. */ - public static final int CMAS_CATEGORY_INFRA = 0x09; - - /** CMAS alert category: Chem, bio, radiological, nuclear, high explosive threat or attack. */ - public static final int CMAS_CATEGORY_CBRNE = 0x0a; - - /** CMAS alert category: Other events. */ - public static final int CMAS_CATEGORY_OTHER = 0x0b; - - /** - * CMAS alert category is unknown. The category is only available for CDMA broadcasts - * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown. - */ - public static final int CMAS_CATEGORY_UNKNOWN = -1; - - // CMAS response type (in CDMA type 1 elements record). - - /** CMAS response type: Take shelter in place. */ - public static final int CMAS_RESPONSE_TYPE_SHELTER = 0x00; - - /** CMAS response type: Evacuate (Relocate). */ - public static final int CMAS_RESPONSE_TYPE_EVACUATE = 0x01; - - /** CMAS response type: Make preparations. */ - public static final int CMAS_RESPONSE_TYPE_PREPARE = 0x02; - - /** CMAS response type: Execute a pre-planned activity. */ - public static final int CMAS_RESPONSE_TYPE_EXECUTE = 0x03; - - /** CMAS response type: Attend to information sources. */ - public static final int CMAS_RESPONSE_TYPE_MONITOR = 0x04; - - /** CMAS response type: Avoid hazard. */ - public static final int CMAS_RESPONSE_TYPE_AVOID = 0x05; - - /** CMAS response type: Evaluate the information in this message (not for public warnings). */ - public static final int CMAS_RESPONSE_TYPE_ASSESS = 0x06; - - /** CMAS response type: No action recommended. */ - public static final int CMAS_RESPONSE_TYPE_NONE = 0x07; - - /** - * CMAS response type is unknown. The response type is only available for CDMA broadcasts - * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown. - */ - public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1; - - // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record). - - /** CMAS severity type: Extraordinary threat to life or property. */ - public static final int CMAS_SEVERITY_EXTREME = 0x0; - - /** CMAS severity type: Significant threat to life or property. */ - public static final int CMAS_SEVERITY_SEVERE = 0x1; - - /** - * CMAS alert severity is unknown. The severity is available for CDMA warning alerts - * containing a type 1 elements record and for all GSM and UMTS alerts except for the - * Presidential-level alert class (Korean Public Alert System Class 0). - */ - public static final int CMAS_SEVERITY_UNKNOWN = -1; - - // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record). - - /** CMAS urgency type: Responsive action should be taken immediately. */ - public static final int CMAS_URGENCY_IMMEDIATE = 0x0; - - /** CMAS urgency type: Responsive action should be taken within the next hour. */ - public static final int CMAS_URGENCY_EXPECTED = 0x1; - - /** - * CMAS alert urgency is unknown. The urgency is available for CDMA warning alerts - * containing a type 1 elements record and for all GSM and UMTS alerts except for the - * Presidential-level alert class (Korean Public Alert System Class 0). - */ - public static final int CMAS_URGENCY_UNKNOWN = -1; - - // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record). - - /** CMAS certainty type: Determined to have occurred or to be ongoing. */ - public static final int CMAS_CERTAINTY_OBSERVED = 0x0; - - /** CMAS certainty type: Likely (probability > ~50%). */ - public static final int CMAS_CERTAINTY_LIKELY = 0x1; - - /** - * CMAS alert certainty is unknown. The certainty is available for CDMA warning alerts - * containing a type 1 elements record and for all GSM and UMTS alerts except for the - * Presidential-level alert class (Korean Public Alert System Class 0). - */ - public static final int CMAS_CERTAINTY_UNKNOWN = -1; - - /** CMAS message class. */ - private final int mMessageClass; - - /** CMAS category. */ - private final int mCategory; - - /** CMAS response type. */ - private final int mResponseType; - - /** CMAS severity. */ - private final int mSeverity; - - /** CMAS urgency. */ - private final int mUrgency; - - /** CMAS certainty. */ - private final int mCertainty; - - /** Create a new SmsCbCmasInfo object with the specified values. */ - public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity, - int urgency, int certainty) { - mMessageClass = messageClass; - mCategory = category; - mResponseType = responseType; - mSeverity = severity; - mUrgency = urgency; - mCertainty = certainty; - } - - /** Create a new SmsCbCmasInfo object from a Parcel. */ - SmsCbCmasInfo(Parcel in) { - mMessageClass = in.readInt(); - mCategory = in.readInt(); - mResponseType = in.readInt(); - mSeverity = in.readInt(); - mUrgency = in.readInt(); - mCertainty = in.readInt(); - } - - /** - * Flatten this object into a Parcel. - * - * @param dest The Parcel in which the object should be written. - * @param flags Additional flags about how the object should be written (ignored). - */ - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mMessageClass); - dest.writeInt(mCategory); - dest.writeInt(mResponseType); - dest.writeInt(mSeverity); - dest.writeInt(mUrgency); - dest.writeInt(mCertainty); - } - - /** - * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}. - * @return one of the {@code CMAS_CLASS} values - */ - public int getMessageClass() { - return mMessageClass; - } - - /** - * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}. - * @return one of the {@code CMAS_CATEGORY} values - */ - public int getCategory() { - return mCategory; - } - - /** - * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}. - * @return one of the {@code CMAS_RESPONSE_TYPE} values - */ - public int getResponseType() { - return mResponseType; - } - - /** - * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}. - * @return one of the {@code CMAS_SEVERITY} values - */ - public int getSeverity() { - return mSeverity; - } - - /** - * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}. - * @return one of the {@code CMAS_URGENCY} values - */ - public int getUrgency() { - return mUrgency; - } - - /** - * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}. - * @return one of the {@code CMAS_CERTAINTY} values - */ - public int getCertainty() { - return mCertainty; - } - - @Override - public String toString() { - return "SmsCbCmasInfo{messageClass=" + mMessageClass + ", category=" + mCategory - + ", responseType=" + mResponseType + ", severity=" + mSeverity - + ", urgency=" + mUrgency + ", certainty=" + mCertainty + '}'; - } - - /** - * Describe the kinds of special objects contained in the marshalled representation. - * @return a bitmask indicating this Parcelable contains no special objects - */ - @Override - public int describeContents() { - return 0; - } - - /** Creator for unparcelling objects. */ - public static final Parcelable.Creator<SmsCbCmasInfo> - CREATOR = new Parcelable.Creator<SmsCbCmasInfo>() { - public SmsCbCmasInfo createFromParcel(Parcel in) { - return new SmsCbCmasInfo(in); - } - - public SmsCbCmasInfo[] newArray(int size) { - return new SmsCbCmasInfo[size]; - } - }; -} diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java b/telephony/java/android/telephony/SmsCbConstants.java index ebb4666..a1b4adf 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java +++ b/telephony/java/android/telephony/SmsCbConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * 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. @@ -14,22 +14,14 @@ * limitations under the License. */ -package com.android.internal.telephony.gsm; +package android.telephony; /** - * Constants used in SMS Cell Broadcast messages (see 3GPP TS 23.041). This class is used by the - * boot-time broadcast channel enable and database upgrade code in CellBroadcastReceiver, so it - * is public, but should be avoided in favor of the radio technology independent constants in - * {@link android.telephony.SmsCbMessage}, {@link android.telephony.SmsCbEtwsInfo}, and - * {@link android.telephony.SmsCbCmasInfo} classes. + * Constants used in SMS Cell Broadcast messages. * * {@hide} */ -public class SmsCbConstants { - - /** Private constructor for utility class. */ - private SmsCbConstants() { } - +public interface SmsCbConstants { /** Start of PWS Message Identifier range (includes ETWS and CMAS). */ public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER = 0x1100; @@ -102,11 +94,11 @@ public class SmsCbConstants { /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */ public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER = 0x18FF; - /** ETWS serial number flag to activate the popup display. */ - public static final int SERIAL_NUMBER_ETWS_ACTIVATE_POPUP = 0x1000; + /** ETWS message code flag to activate the popup display. */ + public static final int MESSAGE_CODE_ETWS_ACTIVATE_POPUP = 0x100; - /** ETWS serial number flag to activate the emergency user alert. */ - public static final int SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT = 0x2000; + /** ETWS message code flag to activate the emergency user alert. */ + public static final int MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT = 0x200; /** ETWS warning type value for earthquake. */ public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00; diff --git a/telephony/java/android/telephony/SmsCbEtwsInfo.java b/telephony/java/android/telephony/SmsCbEtwsInfo.java deleted file mode 100644 index 0890d52..0000000 --- a/telephony/java/android/telephony/SmsCbEtwsInfo.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.telephony; - -import android.os.Parcel; -import android.os.Parcelable; -import android.text.format.Time; - -import com.android.internal.telephony.IccUtils; - -import java.util.Arrays; - -/** - * Contains information elements for a GSM or UMTS ETWS warning notification. - * Supported values for each element are defined in 3GPP TS 23.041. - * - * {@hide} - */ -public class SmsCbEtwsInfo implements Parcelable { - - /** ETWS warning type for earthquake. */ - public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00; - - /** ETWS warning type for tsunami. */ - public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01; - - /** ETWS warning type for earthquake and tsunami. */ - public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02; - - /** ETWS warning type for test messages. */ - public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 0x03; - - /** ETWS warning type for other emergency types. */ - public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 0x04; - - /** Unknown ETWS warning type. */ - public static final int ETWS_WARNING_TYPE_UNKNOWN = -1; - - /** One of the ETWS warning type constants defined in this class. */ - private final int mWarningType; - - /** Whether or not to activate the emergency user alert tone and vibration. */ - private final boolean mEmergencyUserAlert; - - /** Whether or not to activate a popup alert. */ - private final boolean mActivatePopup; - - /** - * 50-byte security information (ETWS primary notification for GSM only). As of Release 10, - * 3GPP TS 23.041 states that the UE shall ignore the ETWS primary notification timestamp - * and digital signature if received. Therefore it is treated as a raw byte array and - * parceled with the broadcast intent if present, but the timestamp is only computed if an - * application asks for the individual components. - */ - private final byte[] mWarningSecurityInformation; - - /** Create a new SmsCbEtwsInfo object with the specified values. */ - public SmsCbEtwsInfo(int warningType, boolean emergencyUserAlert, boolean activatePopup, - byte[] warningSecurityInformation) { - mWarningType = warningType; - mEmergencyUserAlert = emergencyUserAlert; - mActivatePopup = activatePopup; - mWarningSecurityInformation = warningSecurityInformation; - } - - /** Create a new SmsCbEtwsInfo object from a Parcel. */ - SmsCbEtwsInfo(Parcel in) { - mWarningType = in.readInt(); - mEmergencyUserAlert = (in.readInt() != 0); - mActivatePopup = (in.readInt() != 0); - mWarningSecurityInformation = in.createByteArray(); - } - - /** - * Flatten this object into a Parcel. - * - * @param dest The Parcel in which the object should be written. - * @param flags Additional flags about how the object should be written (ignored). - */ - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mWarningType); - dest.writeInt(mEmergencyUserAlert ? 1 : 0); - dest.writeInt(mActivatePopup ? 1 : 0); - dest.writeByteArray(mWarningSecurityInformation); - } - - /** - * Returns the ETWS warning type. - * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE} - */ - public int getWarningType() { - return mWarningType; - } - - /** - * Returns the ETWS emergency user alert flag. - * @return true to notify terminal to activate emergency user alert; false otherwise - */ - public boolean isEmergencyUserAlert() { - return mEmergencyUserAlert; - } - - /** - * Returns the ETWS activate popup flag. - * @return true to notify terminal to activate display popup; false otherwise - */ - public boolean isPopupAlert() { - return mActivatePopup; - } - - /** - * Returns the Warning-Security-Information timestamp (GSM primary notifications only). - * As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received. - * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present - */ - public long getPrimaryNotificationTimestamp() { - if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) { - return 0; - } - - int year = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[0]); - int month = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[1]); - int day = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[2]); - int hour = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[3]); - int minute = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[4]); - int second = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[5]); - - // For the timezone, the most significant bit of the - // least significant nibble is the sign byte - // (meaning the max range of this field is 79 quarter-hours, - // which is more than enough) - - byte tzByte = mWarningSecurityInformation[6]; - - // Mask out sign bit. - int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08))); - - timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset; - - Time time = new Time(Time.TIMEZONE_UTC); - - // We only need to support years above 2000. - time.year = year + 2000; - time.month = month - 1; - time.monthDay = day; - time.hour = hour; - time.minute = minute; - time.second = second; - - // Timezone offset is in quarter hours. - return time.toMillis(true) - (long) (timezoneOffset * 15 * 60 * 1000); - } - - /** - * Returns the digital signature (GSM primary notifications only). As of Release 10, - * 3GPP TS 23.041 states that the UE shall ignore this value if received. - * @return a byte array containing a copy of the primary notification digital signature - */ - public byte[] getPrimaryNotificationSignature() { - if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) { - return null; - } - return Arrays.copyOfRange(mWarningSecurityInformation, 7, 50); - } - - @Override - public String toString() { - return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert=" - + mEmergencyUserAlert + ", activatePopup=" + mActivatePopup + '}'; - } - - /** - * Describe the kinds of special objects contained in the marshalled representation. - * @return a bitmask indicating this Parcelable contains no special objects - */ - @Override - public int describeContents() { - return 0; - } - - /** Creator for unparcelling objects. */ - public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() { - public SmsCbEtwsInfo createFromParcel(Parcel in) { - return new SmsCbEtwsInfo(in); - } - - public SmsCbEtwsInfo[] newArray(int size) { - return new SmsCbEtwsInfo[size]; - } - }; -} diff --git a/telephony/java/android/telephony/SmsCbLocation.java b/telephony/java/android/telephony/SmsCbLocation.java deleted file mode 100644 index 7b5bd0d..0000000 --- a/telephony/java/android/telephony/SmsCbLocation.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.telephony; - -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.telephony.gsm.GsmCellLocation; - -/** - * Represents the location and geographical scope of a cell broadcast message. - * For GSM/UMTS, the Location Area and Cell ID are set when the broadcast - * geographical scope is cell wide or Location Area wide. For CDMA, the - * broadcast geographical scope is always PLMN wide. - * - * @hide - */ -public class SmsCbLocation implements Parcelable { - - /** The PLMN. Note that this field may be an empty string, but isn't allowed to be null. */ - private final String mPlmn; - - private final int mLac; - private final int mCid; - - /** - * Construct an empty location object. This is used for some test cases, and for - * cell broadcasts saved in older versions of the database without location info. - */ - public SmsCbLocation() { - mPlmn = ""; - mLac = -1; - mCid = -1; - } - - /** - * Construct a location object for the PLMN. This class is immutable, so - * the same object can be reused for multiple broadcasts. - */ - public SmsCbLocation(String plmn) { - mPlmn = plmn; - mLac = -1; - mCid = -1; - } - - /** - * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so - * the same object can be reused for multiple broadcasts. - */ - public SmsCbLocation(String plmn, int lac, int cid) { - mPlmn = plmn; - mLac = lac; - mCid = cid; - } - - /** - * Initialize the object from a Parcel. - */ - public SmsCbLocation(Parcel in) { - mPlmn = in.readString(); - mLac = in.readInt(); - mCid = in.readInt(); - } - - /** - * Returns the MCC/MNC of the network as a String. - * @return the PLMN identifier (MCC+MNC) as a String - */ - public String getPlmn() { - return mPlmn; - } - - /** - * Returns the GSM location area code, or UMTS service area code. - * @return location area code, -1 if unknown, 0xffff max legal value - */ - public int getLac() { - return mLac; - } - - /** - * Returns the GSM or UMTS cell ID. - * @return gsm cell id, -1 if unknown, 0xffff max legal value - */ - public int getCid() { - return mCid; - } - - @Override - public int hashCode() { - int hash = mPlmn.hashCode(); - hash = hash * 31 + mLac; - hash = hash * 31 + mCid; - return hash; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o == null || !(o instanceof SmsCbLocation)) { - return false; - } - SmsCbLocation other = (SmsCbLocation) o; - return mPlmn.equals(other.mPlmn) && mLac == other.mLac && mCid == other.mCid; - } - - @Override - public String toString() { - return '[' + mPlmn + ',' + mLac + ',' + mCid + ']'; - } - - /** - * Test whether this location is within the location area of the specified object. - * - * @param area the location area to compare with this location - * @return true if this location is contained within the specified location area - */ - public boolean isInLocationArea(SmsCbLocation area) { - if (mCid != -1 && mCid != area.mCid) { - return false; - } - if (mLac != -1 && mLac != area.mLac) { - return false; - } - return mPlmn.equals(area.mPlmn); - } - - /** - * Test whether this location is within the location area of the CellLocation. - * - * @param plmn the PLMN to use for comparison - * @param lac the Location Area (GSM) or Service Area (UMTS) to compare with - * @param cid the Cell ID to compare with - * @return true if this location is contained within the specified PLMN, LAC, and Cell ID - */ - public boolean isInLocationArea(String plmn, int lac, int cid) { - if (!mPlmn.equals(plmn)) { - return false; - } - - if (mLac != -1 && mLac != lac) { - return false; - } - - if (mCid != -1 && mCid != cid) { - return false; - } - - return true; - } - - /** - * Flatten this object into a Parcel. - * - * @param dest The Parcel in which the object should be written. - * @param flags Additional flags about how the object should be written (ignored). - */ - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mPlmn); - dest.writeInt(mLac); - dest.writeInt(mCid); - } - - public static final Parcelable.Creator<SmsCbLocation> CREATOR - = new Parcelable.Creator<SmsCbLocation>() { - @Override - public SmsCbLocation createFromParcel(Parcel in) { - return new SmsCbLocation(in); - } - - @Override - public SmsCbLocation[] newArray(int size) { - return new SmsCbLocation[size]; - } - }; - - /** - * Describe the kinds of special objects contained in the marshalled representation. - * @return a bitmask indicating this Parcelable contains no special objects - */ - @Override - public int describeContents() { - return 0; - } -} diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java index 046bf8c..383e0f9 100644 --- a/telephony/java/android/telephony/SmsCbMessage.java +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -16,367 +16,444 @@ package android.telephony; -import android.os.Parcel; -import android.os.Parcelable; +import android.text.format.Time; +import android.util.Log; + +import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.IccUtils; +import com.android.internal.telephony.gsm.SmsCbHeader; + +import java.io.UnsupportedEncodingException; /** - * Parcelable object containing a received cell broadcast message. There are four different types - * of Cell Broadcast messages: - * - * <ul> - * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li> - * <li>cell information messages, broadcast on channel 50, indicating the current cell name for - * roaming purposes (required to display on the idle screen in Brazil)</li> - * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li> - * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li> - * </ul> - * - * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only), - * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were - * unified under a common name, avoiding some names, such as "Message Identifier", that refer to - * two completely different concepts in 3GPP and CDMA. - * - * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name - * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP - * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the - * application should - * - * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used - * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is - * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit - * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number - * are considered unique to the PLMN, to the current cell, or to the current Location Area (or - * Service Area in UMTS). The relevant values are concatenated into a single String which will be - * unique if the messages are not duplicates. - * - * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the - * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object. - * - * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive - * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts. - * Only system applications such as the CellBroadcastReceiver may receive notifications for - * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or - * interference with the immediate display of the alert message and playing of the alert sound and - * vibration pattern, which could be caused by poorly written or malicious non-system code. + * Describes an SMS-CB message. * - * @hide + * {@hide} */ -public class SmsCbMessage implements Parcelable { +public class SmsCbMessage { - protected static final String LOG_TAG = "SMSCB"; - - /** Cell wide geographical scope with immediate display (GSM/UMTS only). */ + /** + * Cell wide immediate geographical scope + */ public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0; - /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */ + /** + * PLMN wide geographical scope + */ public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1; - /** Location / service area wide geographical scope (GSM/UMTS only). */ + /** + * Location / service area wide geographical scope + */ public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2; - /** Cell wide geographical scope (GSM/UMTS only). */ + /** + * Cell wide geographical scope + */ public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3; - /** GSM or UMTS format cell broadcast. */ - public static final int MESSAGE_FORMAT_3GPP = 1; - - /** CDMA format cell broadcast. */ - public static final int MESSAGE_FORMAT_3GPP2 = 2; - - /** Normal message priority. */ - public static final int MESSAGE_PRIORITY_NORMAL = 0; - - /** Interactive message priority. */ - public static final int MESSAGE_PRIORITY_INTERACTIVE = 1; - - /** Urgent message priority. */ - public static final int MESSAGE_PRIORITY_URGENT = 2; - - /** Emergency message priority. */ - public static final int MESSAGE_PRIORITY_EMERGENCY = 3; - - /** Format of this message (for interpretation of service category values). */ - private final int mMessageFormat; - - /** Geographical scope of broadcast. */ - private final int mGeographicalScope; - /** - * Serial number of broadcast (message identifier for CDMA, geographical scope + message code + - * update number for GSM/UMTS). The serial number plus the location code uniquely identify - * a cell broadcast for duplicate detection. + * Create an instance of this class from a received PDU + * + * @param pdu PDU bytes + * @return An instance of this class, or null if invalid pdu */ - private final int mSerialNumber; + public static SmsCbMessage createFromPdu(byte[] pdu) { + try { + return new SmsCbMessage(pdu); + } catch (IllegalArgumentException e) { + Log.w(LOG_TAG, "Failed parsing SMS-CB pdu", e); + return null; + } + } + + private static final String LOG_TAG = "SMSCB"; /** - * Location identifier for this message. It consists of the current operator MCC/MNC as a - * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the - * message is not binary 01, the Location Area is included for comparison. If the GS is - * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified. + * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5. */ - private final SmsCbLocation mLocation; + private static final String[] LANGUAGE_CODES_GROUP_0 = { + "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu", + "pl", null + }; /** - * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings, - * the information provided by the category is also available via {@link #getEtwsWarningInfo()} - * or {@link #getCmasWarningInfo()}. + * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5. */ - private final int mServiceCategory; - - /** Message language, as a two-character string, e.g. "en". */ - private final String mLanguage; + private static final String[] LANGUAGE_CODES_GROUP_2 = { + "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null, + null, null + }; - /** Message body, as a String. */ - private final String mBody; + private static final char CARRIAGE_RETURN = 0x0d; - /** Message priority (including emergency priority). */ - private final int mPriority; + private static final int PDU_BODY_PAGE_LENGTH = 82; - /** ETWS warning notification information (ETWS warnings only). */ - private final SmsCbEtwsInfo mEtwsWarningInfo; + private SmsCbHeader mHeader; - /** CMAS warning notification information (CMAS warnings only). */ - private final SmsCbCmasInfo mCmasWarningInfo; + private String mLanguage; - /** - * Create a new SmsCbMessage with the specified data. - */ - public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber, - SmsCbLocation location, int serviceCategory, String language, String body, - int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) { - mMessageFormat = messageFormat; - mGeographicalScope = geographicalScope; - mSerialNumber = serialNumber; - mLocation = location; - mServiceCategory = serviceCategory; - mLanguage = language; - mBody = body; - mPriority = priority; - mEtwsWarningInfo = etwsWarningInfo; - mCmasWarningInfo = cmasWarningInfo; - } + private String mBody; - /** Create a new SmsCbMessage object from a Parcel. */ - public SmsCbMessage(Parcel in) { - mMessageFormat = in.readInt(); - mGeographicalScope = in.readInt(); - mSerialNumber = in.readInt(); - mLocation = new SmsCbLocation(in); - mServiceCategory = in.readInt(); - mLanguage = in.readString(); - mBody = in.readString(); - mPriority = in.readInt(); - int type = in.readInt(); - switch (type) { - case 'E': - // unparcel ETWS warning information - mEtwsWarningInfo = new SmsCbEtwsInfo(in); - mCmasWarningInfo = null; - break; + /** Timestamp of ETWS primary notification with security. */ + private long mPrimaryNotificationTimestamp; - case 'C': - // unparcel CMAS warning information - mEtwsWarningInfo = null; - mCmasWarningInfo = new SmsCbCmasInfo(in); - break; + /** 43 byte digital signature of ETWS primary notification with security. */ + private byte[] mPrimaryNotificationDigitalSignature; - default: - mEtwsWarningInfo = null; - mCmasWarningInfo = null; - } - } - - /** - * Flatten this object into a Parcel. - * - * @param dest The Parcel in which the object should be written. - * @param flags Additional flags about how the object should be written (ignored). - */ - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mMessageFormat); - dest.writeInt(mGeographicalScope); - dest.writeInt(mSerialNumber); - mLocation.writeToParcel(dest, flags); - dest.writeInt(mServiceCategory); - dest.writeString(mLanguage); - dest.writeString(mBody); - dest.writeInt(mPriority); - if (mEtwsWarningInfo != null) { - // parcel ETWS warning information - dest.writeInt('E'); - mEtwsWarningInfo.writeToParcel(dest, flags); - } else if (mCmasWarningInfo != null) { - // parcel CMAS warning information - dest.writeInt('C'); - mCmasWarningInfo.writeToParcel(dest, flags); + private SmsCbMessage(byte[] pdu) throws IllegalArgumentException { + mHeader = new SmsCbHeader(pdu); + if (mHeader.format == SmsCbHeader.FORMAT_ETWS_PRIMARY) { + mBody = "ETWS"; + // ETWS primary notification with security is 56 octets in length + if (pdu.length >= SmsCbHeader.PDU_LENGTH_ETWS) { + mPrimaryNotificationTimestamp = getTimestampMillis(pdu); + mPrimaryNotificationDigitalSignature = new byte[43]; + // digital signature starts after 6 byte header and 7 byte timestamp + System.arraycopy(pdu, 13, mPrimaryNotificationDigitalSignature, 0, 43); + } } else { - // no ETWS or CMAS warning information - dest.writeInt('0'); + parseBody(pdu); } } - public static final Parcelable.Creator<SmsCbMessage> CREATOR - = new Parcelable.Creator<SmsCbMessage>() { - @Override - public SmsCbMessage createFromParcel(Parcel in) { - return new SmsCbMessage(in); - } - - @Override - public SmsCbMessage[] newArray(int size) { - return new SmsCbMessage[size]; - } - }; - /** - * Return the geographical scope of this message (GSM/UMTS only). + * Return the geographical scope of this message, one of + * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE}, + * {@link #GEOGRAPHICAL_SCOPE_PLMN_WIDE}, + * {@link #GEOGRAPHICAL_SCOPE_LA_WIDE}, + * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE} * * @return Geographical scope */ public int getGeographicalScope() { - return mGeographicalScope; + return mHeader.geographicalScope; } /** - * Return the broadcast serial number of broadcast (message identifier for CDMA, or - * geographical scope + message code + update number for GSM/UMTS). The serial number plus - * the location code uniquely identify a cell broadcast for duplicate detection. + * Get the ISO-639-1 language code for this message, or null if unspecified * - * @return the 16-bit CDMA message identifier or GSM/UMTS serial number + * @return Language code */ - public int getSerialNumber() { - return mSerialNumber; + public String getLanguageCode() { + return mLanguage; } /** - * Return the location identifier for this message, consisting of the MCC/MNC as a - * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the - * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the - * cell ID is also included. The {@link SmsCbLocation} object includes a method to test - * if the location is included within another location area or within a PLMN and CellLocation. + * Get the body of this message, or null if no body available * - * @return the geographical location code for duplicate message detection + * @return Body, or null */ - public SmsCbLocation getLocation() { - return mLocation; + public String getMessageBody() { + return mBody; } /** - * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation - * of the category is radio technology specific. For ETWS and CMAS warnings, the information - * provided by the category is available via {@link #getEtwsWarningInfo()} or - * {@link #getCmasWarningInfo()} in a radio technology independent format. + * Get the message identifier of this message (0-65535) * - * @return the radio technology specific service category + * @return Message identifier */ - public int getServiceCategory() { - return mServiceCategory; + public int getMessageIdentifier() { + return mHeader.messageIdentifier; } /** - * Get the ISO-639-1 language code for this message, or null if unspecified + * Get the message code of this message (0-1023) * - * @return Language code + * @return Message code */ - public String getLanguageCode() { - return mLanguage; + public int getMessageCode() { + return mHeader.messageCode; } /** - * Get the body of this message, or null if no body available + * Get the update number of this message (0-15) * - * @return Body, or null + * @return Update number */ - public String getMessageBody() { - return mBody; + public int getUpdateNumber() { + return mHeader.updateNumber; } /** - * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}). - * @return an integer representing 3GPP or 3GPP2 message format + * Get the format of this message. + * @return {@link SmsCbHeader#FORMAT_GSM}, {@link SmsCbHeader#FORMAT_UMTS}, or + * {@link SmsCbHeader#FORMAT_ETWS_PRIMARY} */ public int getMessageFormat() { - return mMessageFormat; + return mHeader.format; } /** - * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL} - * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return - * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}. - * @return an integer representing the message priority + * For ETWS primary notifications, return the emergency user alert flag. + * @return true to notify terminal to activate emergency user alert; false otherwise */ - public int getMessagePriority() { - return mPriority; + public boolean getEtwsEmergencyUserAlert() { + return mHeader.etwsEmergencyUserAlert; } /** - * If this is an ETWS warning notification then this method will return an object containing - * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an - * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte - * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the - * ETWS primary notification timestamp and digital signature if received. - * - * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification + * For ETWS primary notifications, return the popup flag. + * @return true to notify terminal to activate display popup; false otherwise */ - public SmsCbEtwsInfo getEtwsWarningInfo() { - return mEtwsWarningInfo; + public boolean getEtwsPopup() { + return mHeader.etwsPopup; } /** - * If this is a CMAS warning notification then this method will return an object containing - * the CMAS message class, category, response type, severity, urgency and certainty. - * The message class is always present. Severity, urgency and certainty are present for CDMA - * warning notifications containing a type 1 elements record and for GSM and UMTS warnings - * except for the Presidential-level alert category. Category and response type are only - * available for CDMA notifications containing a type 1 elements record. - * - * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification + * For ETWS primary notifications, return the warning type. + * @return a value such as {@link SmsCbConstants#ETWS_WARNING_TYPE_EARTHQUAKE} */ - public SmsCbCmasInfo getCmasWarningInfo() { - return mCmasWarningInfo; + public int getEtwsWarningType() { + return mHeader.etwsWarningType; } /** - * Return whether this message is an emergency (PWS) message type. - * @return true if the message is a public warning notification; false otherwise + * For ETWS primary notifications, return the Warning-Security-Information timestamp. + * @return a timestamp in System.currentTimeMillis() format. */ - public boolean isEmergencyMessage() { - return mPriority == MESSAGE_PRIORITY_EMERGENCY; + public long getEtwsSecurityTimestamp() { + return mPrimaryNotificationTimestamp; } /** - * Return whether this message is an ETWS warning alert. - * @return true if the message is an ETWS warning notification; false otherwise + * For ETWS primary notifications, return the 43 byte digital signature. + * @return a byte array containing a copy of the digital signature */ - public boolean isEtwsMessage() { - return mEtwsWarningInfo != null; + public byte[] getEtwsSecuritySignature() { + return mPrimaryNotificationDigitalSignature.clone(); } /** - * Return whether this message is a CMAS warning alert. - * @return true if the message is a CMAS warning notification; false otherwise + * Parse and unpack the body text according to the encoding in the DCS. + * After completing successfully this method will have assigned the body + * text into mBody, and optionally the language code into mLanguage + * + * @param pdu The pdu */ - public boolean isCmasMessage() { - return mCmasWarningInfo != null; + private void parseBody(byte[] pdu) { + int encoding; + boolean hasLanguageIndicator = false; + + // Extract encoding and language from DCS, as defined in 3gpp TS 23.038, + // section 5. + switch ((mHeader.dataCodingScheme & 0xf0) >> 4) { + case 0x00: + encoding = SmsMessage.ENCODING_7BIT; + mLanguage = LANGUAGE_CODES_GROUP_0[mHeader.dataCodingScheme & 0x0f]; + break; + + case 0x01: + hasLanguageIndicator = true; + if ((mHeader.dataCodingScheme & 0x0f) == 0x01) { + encoding = SmsMessage.ENCODING_16BIT; + } else { + encoding = SmsMessage.ENCODING_7BIT; + } + break; + + case 0x02: + encoding = SmsMessage.ENCODING_7BIT; + mLanguage = LANGUAGE_CODES_GROUP_2[mHeader.dataCodingScheme & 0x0f]; + break; + + case 0x03: + encoding = SmsMessage.ENCODING_7BIT; + break; + + case 0x04: + case 0x05: + switch ((mHeader.dataCodingScheme & 0x0c) >> 2) { + case 0x01: + encoding = SmsMessage.ENCODING_8BIT; + break; + + case 0x02: + encoding = SmsMessage.ENCODING_16BIT; + break; + + case 0x00: + default: + encoding = SmsMessage.ENCODING_7BIT; + break; + } + break; + + case 0x06: + case 0x07: + // Compression not supported + case 0x09: + // UDH structure not supported + case 0x0e: + // Defined by the WAP forum not supported + encoding = SmsMessage.ENCODING_UNKNOWN; + break; + + case 0x0f: + if (((mHeader.dataCodingScheme & 0x04) >> 2) == 0x01) { + encoding = SmsMessage.ENCODING_8BIT; + } else { + encoding = SmsMessage.ENCODING_7BIT; + } + break; + + default: + // Reserved values are to be treated as 7-bit + encoding = SmsMessage.ENCODING_7BIT; + break; + } + + if (mHeader.format == SmsCbHeader.FORMAT_UMTS) { + // Payload may contain multiple pages + int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH]; + + if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) + * nrPages) { + throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match " + + nrPages + " pages"); + } + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < nrPages; i++) { + // Each page is 82 bytes followed by a length octet indicating + // the number of useful octets within those 82 + int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i; + int length = pdu[offset + PDU_BODY_PAGE_LENGTH]; + + if (length > PDU_BODY_PAGE_LENGTH) { + throw new IllegalArgumentException("Page length " + length + + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH); + } + + sb.append(unpackBody(pdu, encoding, offset, length, hasLanguageIndicator)); + } + mBody = sb.toString(); + } else { + // Payload is one single page + int offset = SmsCbHeader.PDU_HEADER_LENGTH; + int length = pdu.length - offset; + + mBody = unpackBody(pdu, encoding, offset, length, hasLanguageIndicator); + } } - @Override - public String toString() { - return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber=" - + mSerialNumber + ", location=" + mLocation + ", serviceCategory=" - + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody - + ", priority=" + mPriority - + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "") - + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}'; + /** + * Unpack body text from the pdu using the given encoding, position and + * length within the pdu + * + * @param pdu The pdu + * @param encoding The encoding, as derived from the DCS + * @param offset Position of the first byte to unpack + * @param length Number of bytes to unpack + * @param hasLanguageIndicator true if the body text is preceded by a + * language indicator. If so, this method will as a side-effect + * assign the extracted language code into mLanguage + * @return Body text + */ + private String unpackBody(byte[] pdu, int encoding, int offset, int length, + boolean hasLanguageIndicator) { + String body = null; + + switch (encoding) { + case SmsMessage.ENCODING_7BIT: + body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7); + + if (hasLanguageIndicator && body != null && body.length() > 2) { + // Language is two GSM characters followed by a CR. + // The actual body text is offset by 3 characters. + mLanguage = body.substring(0, 2); + body = body.substring(3); + } + break; + + case SmsMessage.ENCODING_16BIT: + if (hasLanguageIndicator && pdu.length >= offset + 2) { + // Language is two GSM characters. + // The actual body text is offset by 2 bytes. + mLanguage = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2); + offset += 2; + length -= 2; + } + + try { + body = new String(pdu, offset, (length & 0xfffe), "utf-16"); + } catch (UnsupportedEncodingException e) { + // Eeeek + } + break; + + default: + break; + } + + if (body != null) { + // Remove trailing carriage return + for (int i = body.length() - 1; i >= 0; i--) { + if (body.charAt(i) != CARRIAGE_RETURN) { + body = body.substring(0, i + 1); + break; + } + } + } else { + body = ""; + } + + return body; } /** - * Describe the kinds of special objects contained in the marshalled representation. - * @return a bitmask indicating this Parcelable contains no special objects + * Parses an ETWS primary notification timestamp and returns a currentTimeMillis()-style + * timestamp. Copied from com.android.internal.telephony.gsm.SmsMessage. + * @param pdu the ETWS primary notification PDU to decode + * @return the UTC timestamp from the Warning-Security-Information parameter */ + private long getTimestampMillis(byte[] pdu) { + // Timestamp starts after CB header, in pdu[6] + int year = IccUtils.gsmBcdByteToInt(pdu[6]); + int month = IccUtils.gsmBcdByteToInt(pdu[7]); + int day = IccUtils.gsmBcdByteToInt(pdu[8]); + int hour = IccUtils.gsmBcdByteToInt(pdu[9]); + int minute = IccUtils.gsmBcdByteToInt(pdu[10]); + int second = IccUtils.gsmBcdByteToInt(pdu[11]); + + // For the timezone, the most significant bit of the + // least significant nibble is the sign byte + // (meaning the max range of this field is 79 quarter-hours, + // which is more than enough) + + byte tzByte = pdu[12]; + + // Mask out sign bit. + int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08))); + + timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset; + + Time time = new Time(Time.TIMEZONE_UTC); + + // It's 2006. Should I really support years < 2000? + time.year = year >= 90 ? year + 1900 : year + 2000; + time.month = month - 1; + time.monthDay = day; + time.hour = hour; + time.minute = minute; + time.second = second; + + // Timezone offset is in quarter hours. + return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000); + } + + /** + * Append text to the message body. This is used to concatenate multi-page GSM broadcasts. + * @param body the text to append to this message + */ + public void appendToBody(String body) { + mBody = mBody + body; + } + @Override - public int describeContents() { - return 0; + public String toString() { + return "SmsCbMessage{" + mHeader.toString() + ", language=" + mLanguage + + ", body=\"" + mBody + "\"}"; } } diff --git a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java deleted file mode 100644 index f94efd8..0000000 --- a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.telephony.cdma; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * CDMA Service Category Program Data from SCPT teleservice SMS. - * The CellBroadcastReceiver app receives an Intent with action - * {@link android.provider.Telephony.Sms.Intents#SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION} - * containing an array of these objects to update its list of cell broadcast service categories - * to display. - * - * {@hide} - */ -public class CdmaSmsCbProgramData implements Parcelable { - - /** Delete the specified service category from the list of enabled categories. */ - public static final int OPERATION_DELETE_CATEGORY = 0; - - /** Add the specified service category to the list of enabled categories. */ - public static final int OPERATION_ADD_CATEGORY = 1; - - /** Clear all service categories from the list of enabled categories. */ - public static final int OPERATION_CLEAR_CATEGORIES = 2; - - /** Alert option: no alert. */ - public static final int ALERT_OPTION_NO_ALERT = 0; - - /** Alert option: default alert. */ - public static final int ALERT_OPTION_DEFAULT_ALERT = 1; - - /** Alert option: vibrate alert once. */ - public static final int ALERT_OPTION_VIBRATE_ONCE = 2; - - /** Alert option: vibrate alert - repeat. */ - public static final int ALERT_OPTION_VIBRATE_REPEAT = 3; - - /** Alert option: visual alert once. */ - public static final int ALERT_OPTION_VISUAL_ONCE = 4; - - /** Alert option: visual alert - repeat. */ - public static final int ALERT_OPTION_VISUAL_REPEAT = 5; - - /** Alert option: low-priority alert once. */ - public static final int ALERT_OPTION_LOW_PRIORITY_ONCE = 6; - - /** Alert option: low-priority alert - repeat. */ - public static final int ALERT_OPTION_LOW_PRIORITY_REPEAT = 7; - - /** Alert option: medium-priority alert once. */ - public static final int ALERT_OPTION_MED_PRIORITY_ONCE = 8; - - /** Alert option: medium-priority alert - repeat. */ - public static final int ALERT_OPTION_MED_PRIORITY_REPEAT = 9; - - /** Alert option: high-priority alert once. */ - public static final int ALERT_OPTION_HIGH_PRIORITY_ONCE = 10; - - /** Alert option: high-priority alert - repeat. */ - public static final int ALERT_OPTION_HIGH_PRIORITY_REPEAT = 11; - - /** Service category operation (add/delete/clear). */ - private final int mOperation; - - /** Service category to modify. */ - private final int mCategory; - - /** Language used for service category name (ISO 639 two character code). */ - private final String mLanguage; - - /** Maximum number of messages to store for this service category. */ - private final int mMaxMessages; - - /** Service category alert option. */ - private final int mAlertOption; - - /** Name of service category. */ - private final String mCategoryName; - - /** Create a new CdmaSmsCbProgramData object with the specified values. */ - public CdmaSmsCbProgramData(int operation, int category, String language, int maxMessages, - int alertOption, String categoryName) { - mOperation = operation; - mCategory = category; - mLanguage = language; - mMaxMessages = maxMessages; - mAlertOption = alertOption; - mCategoryName = categoryName; - } - - /** Create a new CdmaSmsCbProgramData object from a Parcel. */ - CdmaSmsCbProgramData(Parcel in) { - mOperation = in.readInt(); - mCategory = in.readInt(); - mLanguage = in.readString(); - mMaxMessages = in.readInt(); - mAlertOption = in.readInt(); - mCategoryName = in.readString(); - } - - /** - * Flatten this object into a Parcel. - * - * @param dest The Parcel in which the object should be written. - * @param flags Additional flags about how the object should be written (ignored). - */ - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mOperation); - dest.writeInt(mCategory); - dest.writeString(mLanguage); - dest.writeInt(mMaxMessages); - dest.writeInt(mAlertOption); - dest.writeString(mCategoryName); - } - - /** - * Returns the service category operation, e.g. {@link #OPERATION_ADD_CATEGORY}. - * @return one of the {@code OPERATION_*} values - */ - public int getOperation() { - return mOperation; - } - - /** - * Returns the CDMA service category to modify. - * @return a 16-bit CDMA service category value - */ - public int getCategory() { - return mCategory; - } - - /** - * Returns the ISO-639-1 language code for the service category name, or null if not present. - * @return a two-digit ISO-639-1 language code, e.g. "en" for English - */ - public String getLanguageCode() { - return mLanguage; - } - - /** - * Returns the maximum number of messages to store for this service category. - * @return the maximum number of messages to store for this service category - */ - public int getMaxMessages() { - return mMaxMessages; - } - - /** - * Returns the service category alert option, e.g. {@link #ALERT_OPTION_DEFAULT_ALERT}. - * @return one of the {@code ALERT_OPTION_*} values - */ - public int getAlertOption() { - return mAlertOption; - } - - /** - * Returns the service category name, in the language specified by {@link #getLanguageCode()}. - * @return an optional service category name - */ - public String getCategoryName() { - return mCategoryName; - } - - @Override - public String toString() { - return "CdmaSmsCbProgramData{operation=" + mOperation + ", category=" + mCategory - + ", language=" + mLanguage + ", max messages=" + mMaxMessages - + ", alert option=" + mAlertOption + ", category name=" + mCategoryName + '}'; - } - - /** - * Describe the kinds of special objects contained in the marshalled representation. - * @return a bitmask indicating this Parcelable contains no special objects - */ - @Override - public int describeContents() { - return 0; - } - - /** Creator for unparcelling objects. */ - public static final Parcelable.Creator<CdmaSmsCbProgramData> - CREATOR = new Parcelable.Creator<CdmaSmsCbProgramData>() { - @Override - public CdmaSmsCbProgramData createFromParcel(Parcel in) { - return new CdmaSmsCbProgramData(in); - } - - @Override - public CdmaSmsCbProgramData[] newArray(int size) { - return new CdmaSmsCbProgramData[size]; - } - }; -} diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java index d7aaa38..a42a267 100644 --- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java @@ -39,7 +39,6 @@ import android.os.SystemProperties; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.provider.Settings; -import android.telephony.SmsCbMessage; import android.telephony.SmsMessage; import android.telephony.ServiceState; import android.util.Log; @@ -1079,16 +1078,16 @@ public abstract class SMSDispatcher extends Handler { } }; - protected void dispatchBroadcastMessage(SmsCbMessage message) { - if (message.isEmergencyMessage()) { + protected void dispatchBroadcastPdus(byte[][] pdus, boolean isEmergencyMessage) { + if (isEmergencyMessage) { Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION); - intent.putExtra("message", message); - Log.d(TAG, "Dispatching emergency SMS CB"); + intent.putExtra("pdus", pdus); + Log.d(TAG, "Dispatching " + pdus.length + " emergency SMS CB pdus"); dispatch(intent, RECEIVE_EMERGENCY_BROADCAST_PERMISSION); } else { Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION); - intent.putExtra("message", message); - Log.d(TAG, "Dispatching SMS CB"); + intent.putExtra("pdus", pdus); + Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus"); dispatch(intent, RECEIVE_SMS_PERMISSION); } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java index 4f75c84..ca8d9ae 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java @@ -30,7 +30,6 @@ import android.os.SystemProperties; import android.preference.PreferenceManager; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; -import android.telephony.SmsCbMessage; import android.telephony.SmsManager; import android.telephony.SmsMessage.MessageClass; import android.util.Log; @@ -98,10 +97,6 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } } - private void handleServiceCategoryProgramData(SmsMessage sms) { - - } - /** {@inheritDoc} */ @Override public int dispatchMessage(SmsMessageBase smsb) { @@ -124,19 +119,8 @@ final class CdmaSMSDispatcher extends SMSDispatcher { return Intents.RESULT_SMS_HANDLED; } - SmsMessage sms = (SmsMessage) smsb; - - // Handle CMAS emergency broadcast messages. - if (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()) { - Log.d(TAG, "Broadcast type message"); - SmsCbMessage message = sms.parseBroadcastSms(); - if (message != null) { - dispatchBroadcastMessage(message); - } - return Intents.RESULT_SMS_HANDLED; - } - // See if we have a network duplicate SMS. + SmsMessage sms = (SmsMessage) smsb; mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint(); if (mLastAcknowledgedSmsFingerprint != null && Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) { @@ -165,9 +149,6 @@ final class CdmaSMSDispatcher extends SMSDispatcher { sms.isStatusReportMessage()) { handleCdmaStatusReport(sms); handled = true; - } else if (SmsEnvelope.TELESERVICE_SCPT == teleService) { - handleServiceCategoryProgramData(sms); - handled = true; } else if ((sms.getUserData() == null)) { if (false) { Log.d(TAG, "Received SMS without user data"); diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index ad76192..1409cab 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -19,10 +19,7 @@ package com.android.internal.telephony.cdma; import android.os.Parcel; import android.os.SystemProperties; import android.telephony.PhoneNumberUtils; -import android.telephony.SmsCbLocation; -import android.telephony.SmsCbMessage; import android.util.Log; - import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; @@ -42,8 +39,6 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import static android.telephony.SmsMessage.MessageClass; - /** * TODO(cleanup): these constants are disturbing... are they not just * different interpretations on one number? And if we did not have @@ -52,6 +47,12 @@ import static android.telephony.SmsMessage.MessageClass; * named CdmaSmsMessage, could it not? */ +import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES; +import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER; +import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS; +import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER; +import static android.telephony.SmsMessage.MessageClass; + /** * TODO(cleanup): internally returning null in many places makes * debugging very hard (among many other reasons) and should be made @@ -191,16 +192,15 @@ public class SmsMessage extends SmsMessageBase { // bearer data countInt = p.readInt(); //p_cur->uBearerDataLen - if (countInt < 0) { - countInt = 0; - } - - data = new byte[countInt]; - for (int index=0; index < countInt; index++) { - data[index] = p.readByte(); + if (countInt >0) { + data = new byte[countInt]; + //p_cur->aBearerData[digitCount] : + for (int index=0; index < countInt; index++) { + data[index] = p.readByte(); + } + env.bearerData = data; + // BD gets further decoded when accessed in SMSDispatcher } - // BD gets further decoded when accessed in SMSDispatcher - env.bearerData = data; // link the the filled objects to the SMS env.origAddress = addr; @@ -732,29 +732,6 @@ public class SmsMessage extends SmsMessageBase { } /** - * Parses a broadcast SMS, possibly containing a CMAS alert. - */ - SmsCbMessage parseBroadcastSms() { - BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory); - if (bData == null) { - Log.w(LOG_TAG, "BearerData.decode() returned null"); - return null; - } - - if (Log.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { - Log.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData)); - } - - String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC); - SmsCbLocation location = new SmsCbLocation(plmn); - - return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2, - SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location, - mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr, - bData.priority, null, bData.cmasWarningInfo); - } - - /** * {@inheritDoc} */ public MessageClass getMessageClass() { diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index e70ff18..6743da0 100755 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -16,29 +16,28 @@ package com.android.internal.telephony.cdma.sms; -import android.content.res.Resources; -import android.telephony.SmsCbCmasInfo; -import android.telephony.SmsCbMessage; +import static android.telephony.SmsMessage.ENCODING_16BIT; +import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES; +import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER; + +import android.util.Log; + import android.telephony.SmsMessage; -import android.telephony.cdma.CdmaSmsCbProgramData; + import android.text.format.Time; -import android.util.Log; -import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.IccUtils; +import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; + import com.android.internal.util.BitwiseInputStream; import com.android.internal.util.BitwiseOutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import android.content.res.Resources; + import java.util.TimeZone; -import static android.telephony.SmsMessage.ENCODING_16BIT; -import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES; -import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER; /** * An object to encode and decode CDMA SMS bearer data. @@ -69,8 +68,8 @@ public final class BearerData { private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE = 0x0F; //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA = 0x10; private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX = 0x11; - private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12; - private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13; + //private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12; + //private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13; private final static byte SUBPARAM_MESSAGE_STATUS = 0x14; //private final static byte SUBPARAM_TP_FAILURE_CAUSE = 0x15; //private final static byte SUBPARAM_ENHANCED_VMN = 0x16; @@ -340,68 +339,12 @@ public final class BearerData { */ public CdmaSmsAddress callbackNumber; - /** - * CMAS warning notification information. - * @see #decodeCmasUserData(BearerData, int) - */ - public SmsCbCmasInfo cmasWarningInfo; - - /** - * The Service Category Program Data subparameter is used to enable and disable - * SMS broadcast service categories to display. If this subparameter is present, - * this field will contain a list of one or more - * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the - * operation(s) to perform. - */ - public List<CdmaSmsCbProgramData> serviceCategoryProgramData; - private static class CodingException extends Exception { public CodingException(String s) { super(s); } } - /** - * Returns the language indicator as a two-character ISO 639 string. - * @return a two character ISO 639 language code - */ - public String getLanguage() { - return getLanguageCodeForValue(language); - } - - /** - * Converts a CDMA language indicator value to an ISO 639 two character language code. - * @param languageValue the CDMA language value to convert - * @return the two character ISO 639 language code for the specified value, or null if unknown - */ - private static String getLanguageCodeForValue(int languageValue) { - switch (languageValue) { - case LANGUAGE_ENGLISH: - return "en"; - - case LANGUAGE_FRENCH: - return "fr"; - - case LANGUAGE_SPANISH: - return "es"; - - case LANGUAGE_JAPANESE: - return "ja"; - - case LANGUAGE_KOREAN: - return "ko"; - - case LANGUAGE_CHINESE: - return "zh"; - - case LANGUAGE_HEBREW: - return "he"; - - default: - return null; - } - } - @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -973,9 +916,6 @@ public final class BearerData { private static String decodeUtf8(byte[] data, int offset, int numFields) throws CodingException { - if (numFields < 0 || (numFields + offset) > data.length) { - throw new CodingException("UTF-8 decode failed: offset or length out of range"); - } try { return new String(data, offset, numFields, "UTF-8"); } catch (java.io.UnsupportedEncodingException ex) { @@ -986,12 +926,11 @@ public final class BearerData { private static String decodeUtf16(byte[] data, int offset, int numFields) throws CodingException { - int byteCount = numFields * 2; - if (byteCount < 0 || (byteCount + offset) > data.length) { - throw new CodingException("UTF-16 decode failed: offset or length out of range"); - } + // Start reading from the next 16-bit aligned boundary after offset. + int padding = offset % 2; + numFields -= (offset + padding) / 2; try { - return new String(data, offset, byteCount, "utf-16be"); + return new String(data, offset, numFields * 2, "utf-16be"); } catch (java.io.UnsupportedEncodingException ex) { throw new CodingException("UTF-16 decode failed: " + ex); } @@ -1049,11 +988,8 @@ public final class BearerData { private static String decodeLatin(byte[] data, int offset, int numFields) throws CodingException { - if (numFields < 0 || (numFields + offset) > data.length) { - throw new CodingException("ISO-8859-1 decode failed: offset or length out of range"); - } try { - return new String(data, offset, numFields, "ISO-8859-1"); + return new String(data, offset, numFields - offset, "ISO-8859-1"); } catch (java.io.UnsupportedEncodingException ex) { throw new CodingException("ISO-8859-1 decode failed: " + ex); } @@ -1096,7 +1032,6 @@ public final class BearerData { userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields); } break; - case UserData.ENCODING_IA5: case UserData.ENCODING_7BIT_ASCII: userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields); @@ -1179,9 +1114,8 @@ public final class BearerData { BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); int dataLen = inStream.available() / 6; // 6-bit packed character encoding. int numFields = bData.userData.numFields; - // dataLen may be > 14 characters due to octet padding - if ((numFields > 14) || (dataLen < numFields)) { - throw new CodingException("IS-91 short message decoding failed"); + if ((dataLen > 14) || (dataLen < numFields)) { + throw new CodingException("IS-91 voicemail status decoding failed"); } StringBuffer strbuf = new StringBuffer(dataLen); for (int i = 0; i < numFields; i++) { @@ -1605,7 +1539,7 @@ public final class BearerData { bData.userResponseCode = inStream.read(8); } if ((! decodeSuccess) || (paramBits > 0)) { - Log.d(LOG_TAG, "USER_RESPONSE_CODE decode " + + Log.d(LOG_TAG, "USER_REPONSE_CODE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } @@ -1614,240 +1548,27 @@ public final class BearerData { return decodeSuccess; } - private static boolean decodeServiceCategoryProgramData(BearerData bData, - BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException - { - if (inStream.available() < 13) { - throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only " - + inStream.available() + " bits available"); - } - - int paramBits = inStream.read(8) * 8; - int msgEncoding = inStream.read(5); - paramBits -= 5; - - if (inStream.available() < paramBits) { - throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only " - + inStream.available() + " bits available (" + paramBits + " bits expected)"); - } - - ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>(); - - final int CATEGORY_FIELD_MIN_SIZE = 6 * 8; - boolean decodeSuccess = false; - while (paramBits >= CATEGORY_FIELD_MIN_SIZE) { - int operation = inStream.read(4); - int category = (inStream.read(8) << 8) | inStream.read(8); - String language = getLanguageCodeForValue(inStream.read(8)); - int maxMessages = inStream.read(8); - int alertOption = inStream.read(4); - int numFields = inStream.read(8); - paramBits -= CATEGORY_FIELD_MIN_SIZE; - - int textBits = getBitsForNumFields(msgEncoding, numFields); - if (paramBits < textBits) { - throw new CodingException("category name is " + textBits + " bits in length," - + " but there are only " + paramBits + " bits available"); - } - - UserData userData = new UserData(); - userData.msgEncoding = msgEncoding; - userData.msgEncodingSet = true; - userData.numFields = numFields; - userData.payload = inStream.readByteArray(textBits); - paramBits -= textBits; - - decodeUserDataPayload(userData, false); - String categoryName = userData.payloadStr; - CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category, - language, maxMessages, alertOption, categoryName); - programDataList.add(programData); - - decodeSuccess = true; - } - - if ((! decodeSuccess) || (paramBits > 0)) { - Log.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " + - (decodeSuccess ? "succeeded" : "failed") + - " (extra bits = " + paramBits + ')'); - } - - inStream.skip(paramBits); - bData.serviceCategoryProgramData = programDataList; - return decodeSuccess; - } - - private static int serviceCategoryToCmasMessageClass(int serviceCategory) { - switch (serviceCategory) { - case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT: - return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; - - case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT: - return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; - - case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT: - return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; - - case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: - return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY; - - case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE: - return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST; - - default: - return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; - } - } - - /** - * Calculates the number of bits to read for the specified number of encoded characters. - * @param msgEncoding the message encoding to use - * @param numFields the number of characters to read. For Shift-JIS and Korean encodings, - * this is the number of bytes to read. - * @return the number of bits to read from the stream - * @throws CodingException if the specified encoding is not supported - */ - private static int getBitsForNumFields(int msgEncoding, int numFields) - throws CodingException { - switch (msgEncoding) { - case UserData.ENCODING_OCTET: - case UserData.ENCODING_SHIFT_JIS: - case UserData.ENCODING_KOREAN: - case UserData.ENCODING_LATIN: - case UserData.ENCODING_LATIN_HEBREW: - return numFields * 8; - - case UserData.ENCODING_IA5: - case UserData.ENCODING_7BIT_ASCII: - case UserData.ENCODING_GSM_7BIT_ALPHABET: - return numFields * 7; - - case UserData.ENCODING_UNICODE_16: - return numFields * 16; - - default: - throw new CodingException("unsupported message encoding (" + msgEncoding + ')'); - } - } - - /** - * CMAS message decoding. - * (See TIA-1149-0-1, CMAS over CDMA) - * - * @param serviceCategory is the service category from the SMS envelope - */ - private static void decodeCmasUserData(BearerData bData, int serviceCategory) - throws BitwiseInputStream.AccessException, CodingException { - BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); - if (inStream.available() < 8) { - throw new CodingException("emergency CB with no CMAE_protocol_version"); - } - int protocolVersion = inStream.read(8); - if (protocolVersion != 0) { - throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion); - } - - int messageClass = serviceCategoryToCmasMessageClass(serviceCategory); - int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN; - int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN; - int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; - int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; - int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; - - while (inStream.available() >= 16) { - int recordType = inStream.read(8); - int recordLen = inStream.read(8); - switch (recordType) { - case 0: // Type 0 elements (Alert text) - UserData alertUserData = new UserData(); - alertUserData.msgEncoding = inStream.read(5); - alertUserData.msgEncodingSet = true; - alertUserData.msgType = 0; - - int numFields; // number of chars to decode - switch (alertUserData.msgEncoding) { - case UserData.ENCODING_OCTET: - case UserData.ENCODING_LATIN: - numFields = recordLen - 1; // subtract 1 byte for encoding - break; - - case UserData.ENCODING_IA5: - case UserData.ENCODING_7BIT_ASCII: - case UserData.ENCODING_GSM_7BIT_ALPHABET: - numFields = ((recordLen * 8) - 5) / 7; // subtract 5 bits for encoding - break; - - case UserData.ENCODING_UNICODE_16: - numFields = (recordLen - 1) / 2; - break; - - default: - numFields = 0; // unsupported encoding - } - - alertUserData.numFields = numFields; - alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5); - decodeUserDataPayload(alertUserData, false); - bData.userData = alertUserData; - break; - - case 1: // Type 1 elements - category = inStream.read(8); - responseType = inStream.read(8); - severity = inStream.read(4); - urgency = inStream.read(4); - certainty = inStream.read(4); - inStream.skip(recordLen * 8 - 28); - break; - - default: - Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType); - inStream.skip(recordLen * 8); - break; - } - } - - bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity, - urgency, certainty); - } - /** * Create BearerData object from serialized representation. * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) * * @param smsData byte array of raw encoded SMS bearer data. - * @return an instance of BearerData. - */ - public static BearerData decode(byte[] smsData) { - return decode(smsData, 0); - } - - private static boolean isCmasAlertCategory(int category) { - return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT - && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE; - } - - /** - * Create BearerData object from serialized representation. - * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) * - * @param smsData byte array of raw encoded SMS bearer data. - * @param serviceCategory the envelope service category (for CMAS alert handling) * @return an instance of BearerData. */ - public static BearerData decode(byte[] smsData, int serviceCategory) { + public static BearerData decode(byte[] smsData) { try { BitwiseInputStream inStream = new BitwiseInputStream(smsData); BearerData bData = new BearerData(); int foundSubparamMask = 0; while (inStream.available() > 0) { + boolean decodeSuccess = false; int subparamId = inStream.read(8); int subparamIdBit = 1 << subparamId; if ((foundSubparamMask & subparamIdBit) != 0) { throw new CodingException("illegal duplicate subparameter (" + subparamId + ")"); } - boolean decodeSuccess; switch (subparamId) { case SUBPARAM_MESSAGE_IDENTIFIER: decodeSuccess = decodeMessageId(bData, inStream); @@ -1903,9 +1624,6 @@ public final class BearerData { case SUBPARAM_MESSAGE_DEPOSIT_INDEX: decodeSuccess = decodeDepositIndex(bData, inStream); break; - case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA: - decodeSuccess = decodeServiceCategoryProgramData(bData, inStream); - break; default: throw new CodingException("unsupported bearer data subparameter (" + subparamId + ")"); @@ -1916,10 +1634,7 @@ public final class BearerData { throw new CodingException("missing MESSAGE_IDENTIFIER subparam"); } if (bData.userData != null) { - if (isCmasAlertCategory(serviceCategory) && bData.priorityIndicatorSet - && bData.priority == SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY) { - decodeCmasUserData(bData, serviceCategory); - } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) { + if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) { if ((foundSubparamMask ^ (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^ (1 << SUBPARAM_USER_DATA)) diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java index f73df56..4a60231 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java @@ -37,7 +37,6 @@ public final class SmsEnvelope { static public final int TELESERVICE_VMN = 0x1003; static public final int TELESERVICE_WAP = 0x1004; static public final int TELESERVICE_WEMT = 0x1005; - static public final int TELESERVICE_SCPT = 0x1006; /** * The following are defined as extensions to the standard teleservices @@ -47,17 +46,14 @@ public final class SmsEnvelope { // number of messages waiting, it's used by some CDMA carriers for a voice mail count. static public final int TELESERVICE_MWI = 0x40000; - // Service Categories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1 - // static final int SERVICE_CATEGORY_EMERGENCY = 0x0001; + // ServiceCategories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1 + //static public final int SERVICECATEGORY_EMERGENCY = 0x0010; //... - // CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1 - public static final int SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT = 0x1000; - public static final int SERVICE_CATEGORY_CMAS_EXTREME_THREAT = 0x1001; - public static final int SERVICE_CATEGORY_CMAS_SEVERE_THREAT = 0x1002; - public static final int SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY = 0x1003; - public static final int SERVICE_CATEGORY_CMAS_TEST_MESSAGE = 0x1004; - public static final int SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE = 0x10ff; + /** + * maximum lengths for fields as defined in ril_cdma_sms.h + */ + static public final int SMS_BEARER_DATA_MAX = 255; /** * Provides the type of a SMS message like point to point, broadcast or acknowledge diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java index 599c2b3..189d97db 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java @@ -32,9 +32,9 @@ public class UserData { public static final int ENCODING_7BIT_ASCII = 0x02; public static final int ENCODING_IA5 = 0x03; public static final int ENCODING_UNICODE_16 = 0x04; - public static final int ENCODING_SHIFT_JIS = 0x05; - public static final int ENCODING_KOREAN = 0x06; - public static final int ENCODING_LATIN_HEBREW = 0x07; + //public static final int ENCODING_SHIFT_JIS = 0x05; + //public static final int ENCODING_KOREAN = 0x06; + //public static final int ENCODING_LATIN_HEBREW = 0x07; public static final int ENCODING_LATIN = 0x08; public static final int ENCODING_GSM_7BIT_ALPHABET = 0x09; public static final int ENCODING_GSM_DCS = 0x0A; diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java index 931c662..8a75f51 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java @@ -26,7 +26,6 @@ import android.os.SystemProperties; import android.provider.Telephony.Sms; import android.provider.Telephony.Sms.Intents; import android.telephony.PhoneNumberUtils; -import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.telephony.SmsManager; import android.telephony.gsm.GsmCellLocation; @@ -319,18 +318,24 @@ public final class GsmSMSDispatcher extends SMSDispatcher { * concatenated message */ private static final class SmsCbConcatInfo { - private final SmsCbHeader mHeader; - private final SmsCbLocation mLocation; - public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) { + private final String mPlmn; + + private final int mLac; + + private final int mCid; + + public SmsCbConcatInfo(SmsCbHeader header, String plmn, int lac, int cid) { mHeader = header; - mLocation = location; + mPlmn = plmn; + mLac = lac; + mCid = cid; } @Override public int hashCode() { - return (mHeader.getSerialNumber() * 31) + mLocation.hashCode(); + return mHeader.messageIdentifier * 31 + mHeader.updateNumber; } @Override @@ -338,28 +343,49 @@ public final class GsmSMSDispatcher extends SMSDispatcher { if (obj instanceof SmsCbConcatInfo) { SmsCbConcatInfo other = (SmsCbConcatInfo)obj; - // Two pages match if they have the same serial number (which includes the - // geographical scope and update number), and both pages belong to the same - // location (PLMN, plus LAC and CID if these are part of the geographical scope). - return mHeader.getSerialNumber() == other.mHeader.getSerialNumber() - && mLocation.equals(other.mLocation); + // Two pages match if all header attributes (except the page + // index) are identical, and both pages belong to the same + // location (which is also determined by the scope parameter) + if (mHeader.geographicalScope == other.mHeader.geographicalScope + && mHeader.messageCode == other.mHeader.messageCode + && mHeader.updateNumber == other.mHeader.updateNumber + && mHeader.messageIdentifier == other.mHeader.messageIdentifier + && mHeader.dataCodingScheme == other.mHeader.dataCodingScheme + && mHeader.nrOfPages == other.mHeader.nrOfPages) { + return matchesLocation(other.mPlmn, other.mLac, other.mCid); + } } return false; } /** - * Compare the location code for this message to the current location code. The match is - * relative to the geographical scope of the message, which determines whether the LAC - * and Cell ID are saved in mLocation or set to -1 to match all values. + * Checks if this concatenation info matches the given location. The + * granularity of the match depends on the geographical scope. * - * @param plmn the current PLMN - * @param lac the current Location Area (GSM) or Service Area (UMTS) - * @param cid the current Cell ID - * @return true if this message is valid for the current location; false otherwise + * @param plmn PLMN + * @param lac Location area code + * @param cid Cell ID + * @return true if matching, false otherwise */ public boolean matchesLocation(String plmn, int lac, int cid) { - return mLocation.isInLocationArea(plmn, lac, cid); + switch (mHeader.geographicalScope) { + case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: + case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: + if (mCid != cid) { + return false; + } + // deliberate fall-through + case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: + if (mLac != lac) { + return false; + } + // deliberate fall-through + case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: + return mPlmn != null && mPlmn.equals(plmn); + } + + return false; } } @@ -395,42 +421,24 @@ public final class GsmSMSDispatcher extends SMSDispatcher { int lac = cellLocation.getLac(); int cid = cellLocation.getCid(); - SmsCbLocation location; - switch (header.getGeographicalScope()) { - case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: - location = new SmsCbLocation(plmn, lac, -1); - break; - - case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: - case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: - location = new SmsCbLocation(plmn, lac, cid); - break; - - case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: - default: - location = new SmsCbLocation(plmn); - break; - } - byte[][] pdus; - int pageCount = header.getNumberOfPages(); - if (pageCount > 1) { + if (header.nrOfPages > 1) { // Multi-page message - SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location); + SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, plmn, lac, cid); // Try to find other pages of the same message pdus = mSmsCbPageMap.get(concatInfo); if (pdus == null) { - // This is the first page of this message, make room for all + // This it the first page of this message, make room for all // pages and keep until complete - pdus = new byte[pageCount][]; + pdus = new byte[header.nrOfPages][]; mSmsCbPageMap.put(concatInfo, pdus); } // Page parameter is one-based - pdus[header.getPageIndex() - 1] = receivedPdu; + pdus[header.pageIndex - 1] = receivedPdu; for (int i = 0; i < pdus.length; i++) { if (pdus[i] == null) { @@ -447,8 +455,8 @@ public final class GsmSMSDispatcher extends SMSDispatcher { pdus[0] = receivedPdu; } - SmsCbMessage message = GsmSmsCbMessage.createSmsCbMessage(header, location, pdus); - dispatchBroadcastMessage(message); + 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/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java deleted file mode 100644 index dc9554a..0000000 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.telephony.gsm; - -import android.telephony.SmsCbLocation; -import android.telephony.SmsCbMessage; -import android.util.Pair; - -import com.android.internal.telephony.GsmAlphabet; - -import java.io.UnsupportedEncodingException; - -/** - * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is - * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases. - */ -public class GsmSmsCbMessage { - - /** - * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5. - */ - private static final String[] LANGUAGE_CODES_GROUP_0 = { - "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu", - "pl", null - }; - - /** - * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5. - */ - private static final String[] LANGUAGE_CODES_GROUP_2 = { - "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null, - null, null - }; - - private static final char CARRIAGE_RETURN = 0x0d; - - private static final int PDU_BODY_PAGE_LENGTH = 82; - - /** Utility class with only static methods. */ - private GsmSmsCbMessage() { } - - /** - * Create a new SmsCbMessage object from a header object plus one or more received PDUs. - * - * @param pdus PDU bytes - */ - static SmsCbMessage createSmsCbMessage(SmsCbHeader header, SmsCbLocation location, - byte[][] pdus) throws IllegalArgumentException { - if (header.isEtwsPrimaryNotification()) { - return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, - header.getGeographicalScope(), header.getSerialNumber(), - location, header.getServiceCategory(), - null, "ETWS", SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, - header.getEtwsInfo(), header.getCmasInfo()); - } else { - String language = null; - StringBuilder sb = new StringBuilder(); - for (byte[] pdu : pdus) { - Pair<String, String> p = parseBody(header, pdu); - language = p.first; - sb.append(p.second); - } - int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY - : SmsCbMessage.MESSAGE_PRIORITY_NORMAL; - - return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, - header.getGeographicalScope(), header.getSerialNumber(), location, - header.getServiceCategory(), language, sb.toString(), priority, - header.getEtwsInfo(), header.getCmasInfo()); - } - } - - /** - * Create a new SmsCbMessage object from one or more received PDUs. This is used by some - * CellBroadcastReceiver test cases, because SmsCbHeader is now package local. - * - * @param location the location (geographical scope) for the message - * @param pdus PDU bytes - */ - public static SmsCbMessage createSmsCbMessage(SmsCbLocation location, byte[][] pdus) - throws IllegalArgumentException { - SmsCbHeader header = new SmsCbHeader(pdus[0]); - return createSmsCbMessage(header, location, pdus); - } - - /** - * Parse and unpack the body text according to the encoding in the DCS. - * After completing successfully this method will have assigned the body - * text into mBody, and optionally the language code into mLanguage - * - * @param header the message header to use - * @param pdu the PDU to decode - * @return a Pair of Strings containing the language and body of the message - */ - private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) { - int encoding; - String language = null; - boolean hasLanguageIndicator = false; - int dataCodingScheme = header.getDataCodingScheme(); - - // Extract encoding and language from DCS, as defined in 3gpp TS 23.038, - // section 5. - switch ((dataCodingScheme & 0xf0) >> 4) { - case 0x00: - encoding = android.telephony.SmsMessage.ENCODING_7BIT; - language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f]; - break; - - case 0x01: - hasLanguageIndicator = true; - if ((dataCodingScheme & 0x0f) == 0x01) { - encoding = android.telephony.SmsMessage.ENCODING_16BIT; - } else { - encoding = android.telephony.SmsMessage.ENCODING_7BIT; - } - break; - - case 0x02: - encoding = android.telephony.SmsMessage.ENCODING_7BIT; - language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f]; - break; - - case 0x03: - encoding = android.telephony.SmsMessage.ENCODING_7BIT; - break; - - case 0x04: - case 0x05: - switch ((dataCodingScheme & 0x0c) >> 2) { - case 0x01: - encoding = android.telephony.SmsMessage.ENCODING_8BIT; - break; - - case 0x02: - encoding = android.telephony.SmsMessage.ENCODING_16BIT; - break; - - case 0x00: - default: - encoding = android.telephony.SmsMessage.ENCODING_7BIT; - break; - } - break; - - case 0x06: - case 0x07: - // Compression not supported - case 0x09: - // UDH structure not supported - case 0x0e: - // Defined by the WAP forum not supported - throw new IllegalArgumentException("Unsupported GSM dataCodingScheme " - + dataCodingScheme); - - case 0x0f: - if (((dataCodingScheme & 0x04) >> 2) == 0x01) { - encoding = android.telephony.SmsMessage.ENCODING_8BIT; - } else { - encoding = android.telephony.SmsMessage.ENCODING_7BIT; - } - break; - - default: - // Reserved values are to be treated as 7-bit - encoding = android.telephony.SmsMessage.ENCODING_7BIT; - break; - } - - if (header.isUmtsFormat()) { - // Payload may contain multiple pages - int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH]; - - if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) - * nrPages) { - throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match " - + nrPages + " pages"); - } - - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < nrPages; i++) { - // Each page is 82 bytes followed by a length octet indicating - // the number of useful octets within those 82 - int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i; - int length = pdu[offset + PDU_BODY_PAGE_LENGTH]; - - if (length > PDU_BODY_PAGE_LENGTH) { - throw new IllegalArgumentException("Page length " + length - + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH); - } - - Pair<String, String> p = unpackBody(pdu, encoding, offset, length, - hasLanguageIndicator, language); - language = p.first; - sb.append(p.second); - } - return new Pair<String, String>(language, sb.toString()); - } else { - // Payload is one single page - int offset = SmsCbHeader.PDU_HEADER_LENGTH; - int length = pdu.length - offset; - - return unpackBody(pdu, encoding, offset, length, hasLanguageIndicator, language); - } - } - - /** - * Unpack body text from the pdu using the given encoding, position and - * length within the pdu - * - * @param pdu The pdu - * @param encoding The encoding, as derived from the DCS - * @param offset Position of the first byte to unpack - * @param length Number of bytes to unpack - * @param hasLanguageIndicator true if the body text is preceded by a - * language indicator. If so, this method will as a side-effect - * assign the extracted language code into mLanguage - * @param language the language to return if hasLanguageIndicator is false - * @return a Pair of Strings containing the language and body of the message - */ - private static Pair<String, String> unpackBody(byte[] pdu, int encoding, int offset, int length, - boolean hasLanguageIndicator, String language) { - String body = null; - - switch (encoding) { - case android.telephony.SmsMessage.ENCODING_7BIT: - body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7); - - if (hasLanguageIndicator && body != null && body.length() > 2) { - // Language is two GSM characters followed by a CR. - // The actual body text is offset by 3 characters. - language = body.substring(0, 2); - body = body.substring(3); - } - break; - - case android.telephony.SmsMessage.ENCODING_16BIT: - if (hasLanguageIndicator && pdu.length >= offset + 2) { - // Language is two GSM characters. - // The actual body text is offset by 2 bytes. - language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2); - offset += 2; - length -= 2; - } - - try { - body = new String(pdu, offset, (length & 0xfffe), "utf-16"); - } catch (UnsupportedEncodingException e) { - // Apparently it wasn't valid UTF-16. - throw new IllegalArgumentException("Error decoding UTF-16 message", e); - } - break; - - default: - break; - } - - if (body != null) { - // Remove trailing carriage return - for (int i = body.length() - 1; i >= 0; i--) { - if (body.charAt(i) != CARRIAGE_RETURN) { - body = body.substring(0, i + 1); - break; - } - } - } else { - body = ""; - } - - return new Pair<String, String>(language, body); - } -} diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java index 5692044..8e6b79b 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java @@ -16,42 +16,28 @@ package com.android.internal.telephony.gsm; -import android.telephony.SmsCbCmasInfo; -import android.telephony.SmsCbEtwsInfo; - -import java.util.Arrays; - -/** - * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by - * CellBroadcastReceiver test cases, but should not be used by applications. - * - * All relevant header information is now sent as a Parcelable - * {@link android.telephony.SmsCbMessage} object in the "message" extra of the - * {@link android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION} or - * {@link android.provider.Telephony.Sms.Intents#SMS_EMERGENCY_CB_RECEIVED_ACTION} intent. - * The raw PDU is no longer sent to SMS CB applications. - */ -class SmsCbHeader { +import android.telephony.SmsCbConstants; +public class SmsCbHeader implements SmsCbConstants { /** * Length of SMS-CB header */ - static final int PDU_HEADER_LENGTH = 6; + public static final int PDU_HEADER_LENGTH = 6; /** * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1 */ - static final int FORMAT_GSM = 1; + public static final int FORMAT_GSM = 1; /** * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2 */ - static final int FORMAT_UMTS = 2; + public static final int FORMAT_UMTS = 2; /** * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3 */ - static final int FORMAT_ETWS_PRIMARY = 3; + public static final int FORMAT_ETWS_PRIMARY = 3; /** * Message type value as defined in 3gpp TS 25.324, section 11.1. @@ -61,34 +47,34 @@ class SmsCbHeader { /** * Length of GSM pdus */ - private static final int PDU_LENGTH_GSM = 88; + public static final int PDU_LENGTH_GSM = 88; /** * Maximum length of ETWS primary message GSM pdus */ - private static final int PDU_LENGTH_ETWS = 56; + public static final int PDU_LENGTH_ETWS = 56; + + public final int geographicalScope; - private final int geographicalScope; + public final int messageCode; - /** The serial number combines geographical scope, message code, and update number. */ - private final int serialNumber; + public final int updateNumber; - /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */ - private final int messageIdentifier; + public final int messageIdentifier; - private final int dataCodingScheme; + public final int dataCodingScheme; - private final int pageIndex; + public final int pageIndex; - private final int nrOfPages; + public final int nrOfPages; - private final int format; + public final int format; - /** ETWS warning notification info. */ - private final SmsCbEtwsInfo mEtwsInfo; + public final boolean etwsEmergencyUserAlert; - /** CMAS warning notification info. */ - private final SmsCbCmasInfo mCmasInfo; + public final boolean etwsPopup; + + public final int etwsWarningType; public SmsCbHeader(byte[] pdu) throws IllegalArgumentException { if (pdu == null || pdu.length < PDU_HEADER_LENGTH) { @@ -97,31 +83,22 @@ class SmsCbHeader { if (pdu.length <= PDU_LENGTH_ETWS) { format = FORMAT_ETWS_PRIMARY; - geographicalScope = (pdu[0] & 0xc0) >> 6; - serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff); + geographicalScope = -1; //not applicable + messageCode = -1; + updateNumber = -1; messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff); dataCodingScheme = -1; pageIndex = -1; nrOfPages = -1; - boolean emergencyUserAlert = (pdu[4] & 0x1) != 0; - boolean activatePopup = (pdu[5] & 0x80) != 0; - int warningType = (pdu[4] & 0xfe) >> 1; - byte[] warningSecurityInfo; - // copy the Warning-Security-Information, if present - if (pdu.length > PDU_HEADER_LENGTH) { - warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length); - } else { - warningSecurityInfo = null; - } - mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup, - warningSecurityInfo); - mCmasInfo = null; - return; // skip the ETWS/CMAS initialization code for regular notifications + etwsEmergencyUserAlert = (pdu[4] & 0x1) != 0; + etwsPopup = (pdu[5] & 0x80) != 0; + etwsWarningType = (pdu[4] & 0xfe) >> 1; } else if (pdu.length <= PDU_LENGTH_GSM) { // GSM pdus are no more than 88 bytes format = FORMAT_GSM; geographicalScope = (pdu[0] & 0xc0) >> 6; - serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff); + messageCode = ((pdu[0] & 0x3f) << 4) | ((pdu[1] & 0xf0) >> 4); + updateNumber = pdu[1] & 0x0f; messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff); dataCodingScheme = pdu[4] & 0xff; @@ -136,6 +113,9 @@ class SmsCbHeader { this.pageIndex = pageIndex; this.nrOfPages = nrOfPages; + etwsEmergencyUserAlert = false; + etwsPopup = false; + etwsWarningType = -1; } else { // UMTS pdus are always at least 90 bytes since the payload includes // a number-of-pages octet and also one length octet per page @@ -149,7 +129,8 @@ class SmsCbHeader { messageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff; geographicalScope = (pdu[3] & 0xc0) >> 6; - serialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff); + messageCode = ((pdu[3] & 0x3f) << 4) | ((pdu[4] & 0xf0) >> 4); + updateNumber = pdu[4] & 0x0f; dataCodingScheme = pdu[5] & 0xff; // We will always consider a UMTS message as having one single page @@ -157,251 +138,75 @@ class SmsCbHeader { // actual payload may contain several pages. pageIndex = 1; nrOfPages = 1; + etwsEmergencyUserAlert = false; + etwsPopup = false; + etwsWarningType = -1; } - - if (isEtwsMessage()) { - boolean emergencyUserAlert = isEtwsEmergencyUserAlert(); - boolean activatePopup = isEtwsPopupAlert(); - int warningType = getEtwsWarningType(); - mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup, null); - mCmasInfo = null; - } else if (isCmasMessage()) { - int messageClass = getCmasMessageClass(); - int severity = getCmasSeverity(); - int urgency = getCmasUrgency(); - int certainty = getCmasCertainty(); - mEtwsInfo = null; - mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, - SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty); - } else { - mEtwsInfo = null; - mCmasInfo = null; - } - } - - int getGeographicalScope() { - return geographicalScope; - } - - int getSerialNumber() { - return serialNumber; - } - - int getServiceCategory() { - return messageIdentifier; - } - - int getDataCodingScheme() { - return dataCodingScheme; - } - - int getPageIndex() { - return pageIndex; - } - - int getNumberOfPages() { - return nrOfPages; - } - - SmsCbEtwsInfo getEtwsInfo() { - return mEtwsInfo; - } - - SmsCbCmasInfo getCmasInfo() { - return mCmasInfo; - } - - /** - * Return whether this broadcast is an emergency (PWS) message type. - * @return true if this message is emergency type; false otherwise - */ - boolean isEmergencyMessage() { - return messageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER - && messageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER; - } - - /** - * Return whether this broadcast is an ETWS emergency message type. - * @return true if this message is ETWS emergency type; false otherwise - */ - private boolean isEtwsMessage() { - return (messageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK) - == SmsCbConstants.MESSAGE_ID_ETWS_TYPE; } /** - * Return whether this broadcast is an ETWS primary notification. - * @return true if this message is an ETWS primary notification; false otherwise + * 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 */ - boolean isEtwsPrimaryNotification() { - return format == FORMAT_ETWS_PRIMARY; + public static boolean isEmergencyMessage(int id) { + return id >= MESSAGE_ID_PWS_FIRST_IDENTIFIER && id <= MESSAGE_ID_PWS_LAST_IDENTIFIER; } /** - * Return whether this broadcast is in UMTS format. - * @return true if this message is in UMTS format; false otherwise + * 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 */ - boolean isUmtsFormat() { - return format == FORMAT_UMTS; + public static boolean isEtwsMessage(int id) { + return (id & MESSAGE_ID_ETWS_TYPE_MASK) == MESSAGE_ID_ETWS_TYPE; } /** - * Return whether this message is a CMAS emergency message type. - * @return true if this message is CMAS emergency type; false otherwise + * 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 */ - private boolean isCmasMessage() { - return messageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER - && messageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER; + public static boolean isCmasMessage(int id) { + return id >= MESSAGE_ID_CMAS_FIRST_IDENTIFIER && id <= MESSAGE_ID_CMAS_LAST_IDENTIFIER; } /** - * Return whether the popup alert flag is set for an ETWS warning notification. + * 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 */ - private boolean isEtwsPopupAlert() { - return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0; + public static boolean isEtwsPopupAlert(int messageCode) { + return (messageCode & MESSAGE_CODE_ETWS_ACTIVATE_POPUP) != 0; } /** - * Return whether the emergency user alert flag is set for an ETWS warning notification. + * 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 */ - private boolean isEtwsEmergencyUserAlert() { - return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0; - } - - /** - * Returns the warning type for an ETWS warning notification. - * This method assumes that the message ID has already been checked for ETWS type. - * - * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24 - */ - private int getEtwsWarningType() { - return messageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING; - } - - /** - * Returns the message class for a CMAS warning notification. - * This method assumes that the message ID has already been checked for CMAS type. - * @return the CMAS message class as defined in {@link SmsCbCmasInfo} - */ - private int getCmasMessageClass() { - switch (messageIdentifier) { - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL: - return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; - - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: - return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; - - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: - return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; - - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY: - return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY; - - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST: - return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST; - - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE: - return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE; - - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE: - return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE; - - default: - return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; - } - } - - /** - * Returns the severity for a CMAS warning notification. This is only available for extreme - * and severe alerts, not for other types such as Presidential Level and AMBER alerts. - * This method assumes that the message ID has already been checked for CMAS type. - * @return the CMAS severity as defined in {@link SmsCbCmasInfo} - */ - private int getCmasSeverity() { - switch (messageIdentifier) { - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: - return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME; - - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: - return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE; - - default: - return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; - } - } - - /** - * Returns the urgency for a CMAS warning notification. This is only available for extreme - * and severe alerts, not for other types such as Presidential Level and AMBER alerts. - * This method assumes that the message ID has already been checked for CMAS type. - * @return the CMAS urgency as defined in {@link SmsCbCmasInfo} - */ - private int getCmasUrgency() { - switch (messageIdentifier) { - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: - return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE; - - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: - return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED; - - default: - return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; - } - } - - /** - * Returns the certainty for a CMAS warning notification. This is only available for extreme - * and severe alerts, not for other types such as Presidential Level and AMBER alerts. - * This method assumes that the message ID has already been checked for CMAS type. - * @return the CMAS certainty as defined in {@link SmsCbCmasInfo} - */ - private int getCmasCertainty() { - switch (messageIdentifier) { - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: - return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED; - - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: - case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: - return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY; - - default: - return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; - } + public static boolean isEtwsEmergencyUserAlert(int messageCode) { + return (messageCode & MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT) != 0; } @Override public String toString() { - return "SmsCbHeader{GS=" + geographicalScope + ", serialNumber=0x" + - Integer.toHexString(serialNumber) + + 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 + '}'; diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java index 82c6944..417aac4 100644 --- a/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java @@ -14,54 +14,34 @@ * limitations under the License. */ -package com.android.internal.telephony.gsm; +package com.android.internal.telephony; -import android.telephony.SmsCbEtwsInfo; -import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.test.AndroidTestCase; import android.util.Log; -import com.android.internal.telephony.IccUtils; - -import java.util.Random; - /** * Test cases for basic SmsCbMessage operations */ public class GsmSmsCbTest extends AndroidTestCase { - private static final String TAG = "GsmSmsCbTest"; - - private static final SmsCbLocation sTestLocation = new SmsCbLocation("94040", 1234, 5678); - - private static SmsCbMessage createFromPdu(byte[] pdu) { - try { - SmsCbHeader header = new SmsCbHeader(pdu); - byte[][] pdus = new byte[1][]; - pdus[0] = pdu; - return GsmSmsCbMessage.createSmsCbMessage(header, sTestLocation, pdus); - } catch (IllegalArgumentException e) { - return null; - } - } - - private static void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) { + private void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) { pdu[0] = b; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected geographical scope decoded", expectedGs, msg .getGeographicalScope()); } public void testCreateNullPdu() { - SmsCbMessage msg = createFromPdu(null); + SmsCbMessage msg = SmsCbMessage.createFromPdu(null); + assertNull("createFromPdu(byte[] with null pdu should return null", msg); } public void testCreateTooShortPdu() { byte[] pdu = new byte[4]; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertNull("createFromPdu(byte[] with too short pdu should return null", msg); } @@ -114,7 +94,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x34 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected geographical scope decoded", SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE, msg.getGeographicalScope()); @@ -136,7 +116,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A GSM default alphabet message with carriage return padding", @@ -166,7 +146,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x34 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A GSM default alphabet message with carriage return padding", @@ -213,7 +193,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x0A }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected multipage 7-bit string decoded", "First page+Second page", @@ -236,7 +216,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x90, (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4, (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals( "Unexpected 7-bit string decoded", @@ -268,7 +248,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x52 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals( "Unexpected 7-bit string decoded", @@ -293,7 +273,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A GSM default alphabet message with carriage return padding", @@ -318,7 +298,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A GSM default alphabet message with carriage return padding", @@ -350,7 +330,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x37 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A GSM default alphabet message with carriage return padding", @@ -375,7 +355,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("8-bit message body should be empty", "", msg.getMessageBody()); } @@ -396,7 +376,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A UCS2 message containing a \u0434 character", msg.getMessageBody()); @@ -425,7 +405,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x4E }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A UCS2 message containing a \u0434 character", msg.getMessageBody()); @@ -471,7 +451,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x06 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected multipage UCS2 string decoded", "AAABBB", msg.getMessageBody()); @@ -493,7 +473,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x61, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A UCS2 message containing a \u0434 character", msg.getMessageBody()); @@ -524,7 +504,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x50 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A UCS2 message containing a \u0434 character", msg.getMessageBody()); @@ -549,9 +529,9 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); - assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory()); + assertEquals("Unexpected message identifier decoded", 12345, msg.getMessageIdentifier()); } public void testGetMessageIdentifierUmts() { @@ -578,9 +558,9 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x34 }; - SmsCbMessage msg = createFromPdu(pdu); + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); - assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory()); + assertEquals("Unexpected message identifier decoded", 12345, msg.getMessageIdentifier()); } public void testGetMessageCode() { @@ -600,10 +580,9 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = createFromPdu(pdu); - int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); - assertEquals("Unexpected message code decoded", 682, messageCode); + assertEquals("Unexpected message code decoded", 682, msg.getMessageCode()); } public void testGetMessageCodeUmts() { @@ -630,10 +609,9 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x34 }; - SmsCbMessage msg = createFromPdu(pdu); - int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); - assertEquals("Unexpected message code decoded", 682, messageCode); + assertEquals("Unexpected message code decoded", 682, msg.getMessageCode()); } public void testGetUpdateNumber() { @@ -653,10 +631,9 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = createFromPdu(pdu); - int updateNumber = msg.getSerialNumber() & 0x000f; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); - assertEquals("Unexpected update number decoded", 5, updateNumber); + assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber()); } public void testGetUpdateNumberUmts() { @@ -683,10 +660,9 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x34 }; - SmsCbMessage msg = createFromPdu(pdu); - int updateNumber = msg.getSerialNumber() & 0x000f; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); - assertEquals("Unexpected update number decoded", 5, updateNumber); + assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber()); } /* ETWS Test message including header */ @@ -708,51 +684,29 @@ public class GsmSmsCbTest extends AndroidTestCase { // FIXME: add example of ETWS primary notification PDU public void testEtwsMessageNormal() { - SmsCbMessage msg = createFromPdu(etwsMessageNormal); - Log.d(TAG, msg.toString()); + SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageNormal); + Log.d("GsmSmsCbTest", msg.toString()); assertEquals("GS mismatch", 0, msg.getGeographicalScope()); - assertEquals("serial number mismatch", 0, msg.getSerialNumber()); - assertEquals("message ID mismatch", 0x1100, msg.getServiceCategory()); - assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE, - msg.getEtwsWarningInfo().getWarningType()); + assertEquals("message code mismatch", 0, msg.getMessageCode()); + assertEquals("update number mismatch", 0, msg.getUpdateNumber()); + assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier()); } public void testEtwsMessageCancel() { - SmsCbMessage msg = createFromPdu(etwsMessageCancel); - Log.d(TAG, msg.toString()); + SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageCancel); + Log.d("GsmSmsCbTest", msg.toString()); assertEquals("GS mismatch", 0, msg.getGeographicalScope()); - assertEquals("serial number mismatch", 0, msg.getSerialNumber()); - assertEquals("message ID mismatch", 0x1100, msg.getServiceCategory()); - assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE, - msg.getEtwsWarningInfo().getWarningType()); + assertEquals("message code mismatch", 0, msg.getMessageCode()); + assertEquals("update number mismatch", 0, msg.getUpdateNumber()); + assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier()); } public void testEtwsMessageTest() { - SmsCbMessage msg = createFromPdu(etwsMessageTest); - Log.d(TAG, msg.toString()); + SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageTest); + Log.d("GsmSmsCbTest", msg.toString()); assertEquals("GS mismatch", 0, msg.getGeographicalScope()); - assertEquals("serial number mismatch", 0, msg.getSerialNumber()); - assertEquals("message ID mismatch", 0x1103, msg.getServiceCategory()); - assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE, - msg.getEtwsWarningInfo().getWarningType()); - } - - // Make sure we don't throw an exception if we feed random data to the PDU parser. - public void testRandomPdus() { - Random r = new Random(94040); - for (int run = 0; run < 10000; run++) { - int len = r.nextInt(140); - byte[] data = new byte[len]; - for (int i = 0; i < len; i++) { - data[i] = (byte) r.nextInt(256); - } - try { - // this should return a SmsCbMessage object or null for invalid data - SmsCbMessage msg = createFromPdu(data); - } catch (Exception e) { - Log.d(TAG, "exception thrown", e); - fail("Exception in decoder at run " + run + " length " + len + ": " + e); - } - } + assertEquals("message code mismatch", 0, msg.getMessageCode()); + assertEquals("update number mismatch", 0, msg.getUpdateNumber()); + assertEquals("message ID mismatch", 0x1103, msg.getMessageIdentifier()); } } diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java deleted file mode 100644 index 81f7905..0000000 --- a/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.telephony.cdma; - -import android.os.Parcel; -import android.telephony.SmsCbCmasInfo; -import android.telephony.SmsCbMessage; -import android.test.AndroidTestCase; -import android.util.Log; - -import com.android.internal.telephony.GsmAlphabet; -import com.android.internal.telephony.IccUtils; -import com.android.internal.telephony.cdma.sms.BearerData; -import com.android.internal.telephony.cdma.sms.CdmaSmsAddress; -import com.android.internal.telephony.cdma.sms.SmsEnvelope; -import com.android.internal.telephony.cdma.sms.UserData; -import com.android.internal.util.BitwiseOutputStream; - -import java.util.Arrays; -import java.util.Random; - -/** - * Test cases for basic SmsCbMessage operation for CDMA. - */ -public class CdmaSmsCbTest extends AndroidTestCase { - - /* Copy of private subparameter identifier constants from BearerData class. */ - private static final byte SUBPARAM_MESSAGE_IDENTIFIER = (byte) 0x00; - private static final byte SUBPARAM_USER_DATA = (byte) 0x01; - private static final byte SUBPARAM_PRIORITY_INDICATOR = (byte) 0x08; - private static final byte SUBPARAM_LANGUAGE_INDICATOR = (byte) 0x0D; - - /** - * Initialize a Parcel for an incoming CDMA cell broadcast. The caller will write the - * bearer data and then convert it to an SmsMessage. - * @param serviceCategory the CDMA service category - * @return the initialized Parcel - */ - private static Parcel createBroadcastParcel(int serviceCategory) { - Parcel p = Parcel.obtain(); - - p.writeInt(SmsEnvelope.TELESERVICE_NOT_SET); - p.writeByte((byte) 1); // non-zero for MESSAGE_TYPE_BROADCAST - p.writeInt(serviceCategory); - - // dummy address (RIL may generate a different dummy address for broadcasts) - p.writeInt(CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF); // sAddress.digit_mode - p.writeInt(CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK); // sAddress.number_mode - p.writeInt(CdmaSmsAddress.TON_UNKNOWN); // sAddress.number_type - p.writeInt(CdmaSmsAddress.NUMBERING_PLAN_ISDN_TELEPHONY); // sAddress.number_plan - p.writeByte((byte) 0); // sAddress.number_of_digits - p.writeInt((byte) 0); // sSubAddress.subaddressType - p.writeByte((byte) 0); // sSubAddress.odd - p.writeByte((byte) 0); // sSubAddress.number_of_digits - return p; - } - - /** - * Initialize a BitwiseOutputStream with the CDMA bearer data subparameters except for - * user data. The caller will append the user data and add it to the parcel. - * @param messageId the 16-bit message identifier - * @param priority message priority - * @param language message language code - * @return the initialized BitwiseOutputStream - */ - private static BitwiseOutputStream createBearerDataStream(int messageId, int priority, - int language) throws BitwiseOutputStream.AccessException { - BitwiseOutputStream bos = new BitwiseOutputStream(10); - bos.write(8, SUBPARAM_MESSAGE_IDENTIFIER); - bos.write(8, 3); // length: 3 bytes - bos.write(4, BearerData.MESSAGE_TYPE_DELIVER); - bos.write(8, ((messageId >>> 8) & 0xff)); - bos.write(8, (messageId & 0xff)); - bos.write(1, 0); // no User Data Header - bos.write(3, 0); // reserved - - if (priority != -1) { - bos.write(8, SUBPARAM_PRIORITY_INDICATOR); - bos.write(8, 1); // length: 1 byte - bos.write(2, (priority & 0x03)); - bos.write(6, 0); // reserved - } - - if (language != -1) { - bos.write(8, SUBPARAM_LANGUAGE_INDICATOR); - bos.write(8, 1); // length: 1 byte - bos.write(8, (language & 0xff)); - } - - return bos; - } - - /** - * Write the bearer data array to the parcel, then return a new SmsMessage from the parcel. - * @param p the parcel containing the CDMA SMS headers - * @param bearerData the bearer data byte array to append to the parcel - * @return the new SmsMessage created from the parcel - */ - private static SmsMessage createMessageFromParcel(Parcel p, byte[] bearerData) { - p.writeInt(bearerData.length); - for (byte b : bearerData) { - p.writeByte(b); - } - p.setDataPosition(0); // reset position for reading - SmsMessage message = SmsMessage.newFromParcel(p); - p.recycle(); - return message; - } - - /** - * Create a parcel for an incoming CMAS broadcast, then return a new SmsMessage created - * from the parcel. - * @param serviceCategory the CDMA service category - * @param messageId the 16-bit message identifier - * @param priority message priority - * @param language message language code - * @param body message body - * @param cmasCategory CMAS category (or -1 to skip adding CMAS type 1 elements record) - * @param responseType CMAS response type - * @param severity CMAS severity - * @param urgency CMAS urgency - * @param certainty CMAS certainty - * @return the newly created SmsMessage object - */ - private static SmsMessage createCmasSmsMessage(int serviceCategory, int messageId, int priority, - int language, int encoding, String body, int cmasCategory, int responseType, - int severity, int urgency, int certainty) throws Exception { - BitwiseOutputStream cmasBos = new BitwiseOutputStream(10); - cmasBos.write(8, 0); // CMAE protocol version 0 - - if (body != null) { - cmasBos.write(8, 0); // Type 0 elements (alert text) - encodeBody(encoding, body, true, cmasBos); - } - - if (cmasCategory != SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN) { - cmasBos.write(8, 1); // Type 1 elements - cmasBos.write(8, 4); // length: 4 bytes - cmasBos.write(8, (cmasCategory & 0xff)); - cmasBos.write(8, (responseType & 0xff)); - cmasBos.write(4, (severity & 0x0f)); - cmasBos.write(4, (urgency & 0x0f)); - cmasBos.write(4, (certainty & 0x0f)); - cmasBos.write(4, 0); // pad to octet boundary - } - - byte[] cmasUserData = cmasBos.toByteArray(); - - Parcel p = createBroadcastParcel(serviceCategory); - BitwiseOutputStream bos = createBearerDataStream(messageId, priority, language); - - bos.write(8, SUBPARAM_USER_DATA); - bos.write(8, cmasUserData.length + 2); // add 2 bytes for msg_encoding and num_fields - bos.write(5, UserData.ENCODING_OCTET); - bos.write(8, cmasUserData.length); - bos.writeByteArray(cmasUserData.length * 8, cmasUserData); - bos.write(3, 0); // pad to byte boundary - - return createMessageFromParcel(p, bos.toByteArray()); - } - - /** - * Create a parcel for an incoming CDMA cell broadcast, then return a new SmsMessage created - * from the parcel. - * @param serviceCategory the CDMA service category - * @param messageId the 16-bit message identifier - * @param priority message priority - * @param language message language code - * @param encoding user data encoding method - * @param body the message body - * @return the newly created SmsMessage object - */ - private static SmsMessage createBroadcastSmsMessage(int serviceCategory, int messageId, - int priority, int language, int encoding, String body) throws Exception { - Parcel p = createBroadcastParcel(serviceCategory); - BitwiseOutputStream bos = createBearerDataStream(messageId, priority, language); - - bos.write(8, SUBPARAM_USER_DATA); - encodeBody(encoding, body, false, bos); - - return createMessageFromParcel(p, bos.toByteArray()); - } - - /** - * Append the message length, encoding, and body to the BearerData output stream. - * This is used for writing the User Data subparameter for non-CMAS broadcasts and for - * writing the alert text for CMAS broadcasts. - * @param encoding one of the CDMA UserData encoding values - * @param body the message body - * @param isCmasRecord true if this is a CMAS type 0 elements record; false for user data - * @param bos the BitwiseOutputStream to write to - * @throws Exception on any encoding error - */ - private static void encodeBody(int encoding, String body, boolean isCmasRecord, - BitwiseOutputStream bos) throws Exception { - if (encoding == UserData.ENCODING_7BIT_ASCII || encoding == UserData.ENCODING_IA5) { - int charCount = body.length(); - int recordBits = (charCount * 7) + 5; // add 5 bits for char set field - int recordOctets = (recordBits + 7) / 8; // round up to octet boundary - int padBits = (recordOctets * 8) - recordBits; - - if (!isCmasRecord) { - recordOctets++; // add 8 bits for num_fields - } - - bos.write(8, recordOctets); - bos.write(5, (encoding & 0x1f)); - - if (!isCmasRecord) { - bos.write(8, charCount); - } - - for (int i = 0; i < charCount; i++) { - bos.write(7, body.charAt(i)); - } - - bos.write(padBits, 0); // pad to octet boundary - } else if (encoding == UserData.ENCODING_GSM_7BIT_ALPHABET - || encoding == UserData.ENCODING_GSM_DCS) { - // convert to 7-bit packed encoding with septet count in index 0 of byte array - byte[] encodedBody = GsmAlphabet.stringToGsm7BitPacked(body); - - int charCount = encodedBody[0]; // septet count - int recordBits = (charCount * 7) + 5; // add 5 bits for char set field - int recordOctets = (recordBits + 7) / 8; // round up to octet boundary - int padBits = (recordOctets * 8) - recordBits; - - if (!isCmasRecord) { - recordOctets++; // add 8 bits for num_fields - if (encoding == UserData.ENCODING_GSM_DCS) { - recordOctets++; // add 8 bits for DCS (message type) - } - } - - bos.write(8, recordOctets); - bos.write(5, (encoding & 0x1f)); - - if (!isCmasRecord && encoding == UserData.ENCODING_GSM_DCS) { - bos.write(8, 0); // GSM DCS: 7 bit default alphabet, no msg class - } - - if (!isCmasRecord) { - bos.write(8, charCount); - } - byte[] bodySeptets = Arrays.copyOfRange(encodedBody, 1, encodedBody.length); - bos.writeByteArray(charCount * 7, bodySeptets); - bos.write(padBits, 0); // pad to octet boundary - } else if (encoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) { - // 6 bit packed encoding with 0x20 offset (ASCII 0x20 - 0x60) - int charCount = body.length(); - int recordBits = (charCount * 6) + 21; // add 21 bits for header fields - int recordOctets = (recordBits + 7) / 8; // round up to octet boundary - int padBits = (recordOctets * 8) - recordBits; - - bos.write(8, recordOctets); - - bos.write(5, (encoding & 0x1f)); - bos.write(8, UserData.IS91_MSG_TYPE_SHORT_MESSAGE); - bos.write(8, charCount); - - for (int i = 0; i < charCount; i++) { - bos.write(6, ((int) body.charAt(i) - 0x20)); - } - - bos.write(padBits, 0); // pad to octet boundary - } else { - byte[] encodedBody; - switch (encoding) { - case UserData.ENCODING_UNICODE_16: - encodedBody = body.getBytes("UTF-16BE"); - break; - - case UserData.ENCODING_SHIFT_JIS: - encodedBody = body.getBytes("Shift_JIS"); - break; - - case UserData.ENCODING_KOREAN: - encodedBody = body.getBytes("KSC5601"); - break; - - case UserData.ENCODING_LATIN_HEBREW: - encodedBody = body.getBytes("ISO-8859-8"); - break; - - case UserData.ENCODING_LATIN: - default: - encodedBody = body.getBytes("ISO-8859-1"); - break; - } - int charCount = body.length(); // use actual char count for num fields - int recordOctets = encodedBody.length + 1; // add 1 byte for encoding and pad bits - if (!isCmasRecord) { - recordOctets++; // add 8 bits for num_fields - } - bos.write(8, recordOctets); - bos.write(5, (encoding & 0x1f)); - if (!isCmasRecord) { - bos.write(8, charCount); - } - bos.writeByteArray(encodedBody.length * 8, encodedBody); - bos.write(3, 0); // pad to octet boundary - } - } - - private static final String TEST_TEXT = "This is a test CDMA cell broadcast message..." - + "678901234567890123456789012345678901234567890"; - - private static final String PRES_ALERT = - "THE PRESIDENT HAS ISSUED AN EMERGENCY ALERT. CHECK LOCAL MEDIA FOR MORE DETAILS"; - - private static final String EXTREME_ALERT = "FLASH FLOOD WARNING FOR SOUTH COCONINO COUNTY" - + " - NORTH CENTRAL ARIZONA UNTIL 415 PM MST"; - - private static final String SEVERE_ALERT = "SEVERE WEATHER WARNING FOR SOMERSET COUNTY" - + " - NEW JERSEY UNTIL 415 PM MST"; - - private static final String AMBER_ALERT = - "AMBER ALERT:Mountain View,CA VEH'07 Blue Honda Civic CA LIC 5ABC123"; - - private static final String MONTHLY_TEST_ALERT = "This is a test of the emergency alert system." - + " This is only a test. 89012345678901234567890"; - - private static final String IS91_TEXT = "IS91 SHORT MSG"; // max length 14 chars - - /** - * Verify that the SmsCbMessage has the correct values for CDMA. - * @param cbMessage the message to test - */ - private static void verifyCbValues(SmsCbMessage cbMessage) { - assertEquals(SmsCbMessage.MESSAGE_FORMAT_3GPP2, cbMessage.getMessageFormat()); - assertEquals(SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, cbMessage.getGeographicalScope()); - assertEquals(false, cbMessage.isEtwsMessage()); // ETWS on CDMA not currently supported - } - - private static void doTestNonEmergencyBroadcast(int encoding) throws Exception { - SmsMessage msg = createBroadcastSmsMessage(123, 456, BearerData.PRIORITY_NORMAL, - BearerData.LANGUAGE_ENGLISH, encoding, TEST_TEXT); - - SmsCbMessage cbMessage = msg.parseBroadcastSms(); - verifyCbValues(cbMessage); - assertEquals(123, cbMessage.getServiceCategory()); - assertEquals(456, cbMessage.getSerialNumber()); - assertEquals(SmsCbMessage.MESSAGE_PRIORITY_NORMAL, cbMessage.getMessagePriority()); - assertEquals("en", cbMessage.getLanguageCode()); - assertEquals(TEST_TEXT, cbMessage.getMessageBody()); - assertEquals(false, cbMessage.isEmergencyMessage()); - assertEquals(false, cbMessage.isCmasMessage()); - } - - public void testNonEmergencyBroadcast7bitAscii() throws Exception { - doTestNonEmergencyBroadcast(UserData.ENCODING_7BIT_ASCII); - } - - public void testNonEmergencyBroadcast7bitGsm() throws Exception { - doTestNonEmergencyBroadcast(UserData.ENCODING_GSM_7BIT_ALPHABET); - } - - public void testNonEmergencyBroadcast16bitUnicode() throws Exception { - doTestNonEmergencyBroadcast(UserData.ENCODING_UNICODE_16); - } - - public void testNonEmergencyBroadcastIs91Extended() throws Exception { - // IS-91 doesn't support language or priority subparameters, max 14 chars text - SmsMessage msg = createBroadcastSmsMessage(987, 654, -1, -1, - UserData.ENCODING_IS91_EXTENDED_PROTOCOL, IS91_TEXT); - - SmsCbMessage cbMessage = msg.parseBroadcastSms(); - verifyCbValues(cbMessage); - assertEquals(987, cbMessage.getServiceCategory()); - assertEquals(654, cbMessage.getSerialNumber()); - assertEquals(SmsCbMessage.MESSAGE_PRIORITY_NORMAL, cbMessage.getMessagePriority()); - assertEquals(null, cbMessage.getLanguageCode()); - assertEquals(IS91_TEXT, cbMessage.getMessageBody()); - assertEquals(false, cbMessage.isEmergencyMessage()); - assertEquals(false, cbMessage.isCmasMessage()); - } - - private static void doTestCmasBroadcast(int serviceCategory, int messageClass, String body) - throws Exception { - SmsMessage msg = createCmasSmsMessage( - serviceCategory, 1234, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH, - UserData.ENCODING_7BIT_ASCII, body, -1, -1, -1, -1, -1); - - SmsCbMessage cbMessage = msg.parseBroadcastSms(); - verifyCbValues(cbMessage); - assertEquals(serviceCategory, cbMessage.getServiceCategory()); - assertEquals(1234, cbMessage.getSerialNumber()); - assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority()); - assertEquals("en", cbMessage.getLanguageCode()); - assertEquals(body, cbMessage.getMessageBody()); - assertEquals(true, cbMessage.isEmergencyMessage()); - assertEquals(true, cbMessage.isCmasMessage()); - SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo(); - assertEquals(messageClass, cmasInfo.getMessageClass()); - assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, cmasInfo.getCategory()); - assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, cmasInfo.getResponseType()); - assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN, cmasInfo.getSeverity()); - assertEquals(SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, cmasInfo.getUrgency()); - assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN, cmasInfo.getCertainty()); - } - - public void testCmasPresidentialAlert() throws Exception { - doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, - SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, PRES_ALERT); - } - - public void testCmasExtremeAlert() throws Exception { - doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, - SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT, EXTREME_ALERT); - } - - public void testCmasSevereAlert() throws Exception { - doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, - SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT, SEVERE_ALERT); - } - - public void testCmasAmberAlert() throws Exception { - doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, - SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY, AMBER_ALERT); - } - - public void testCmasTestMessage() throws Exception { - doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE, - SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST, MONTHLY_TEST_ALERT); - } - - public void testCmasExtremeAlertType1Elements() throws Exception { - SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, - 5678, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH, - UserData.ENCODING_7BIT_ASCII, EXTREME_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_ENV, - SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, SmsCbCmasInfo.CMAS_SEVERITY_SEVERE, - SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY); - - SmsCbMessage cbMessage = msg.parseBroadcastSms(); - verifyCbValues(cbMessage); - assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, - cbMessage.getServiceCategory()); - assertEquals(5678, cbMessage.getSerialNumber()); - assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority()); - assertEquals("en", cbMessage.getLanguageCode()); - assertEquals(EXTREME_ALERT, cbMessage.getMessageBody()); - assertEquals(true, cbMessage.isEmergencyMessage()); - assertEquals(true, cbMessage.isCmasMessage()); - SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo(); - assertEquals(SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT, cmasInfo.getMessageClass()); - assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_ENV, cmasInfo.getCategory()); - assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, cmasInfo.getResponseType()); - assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_SEVERE, cmasInfo.getSeverity()); - assertEquals(SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, cmasInfo.getUrgency()); - assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY, cmasInfo.getCertainty()); - } - - // VZW requirement is to discard message with unsupported charset. Verify that we return null - // for this unsupported character set. - public void testCmasUnsupportedCharSet() throws Exception { - SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, - 12345, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH, - UserData.ENCODING_GSM_DCS, EXTREME_ALERT, -1, -1, -1, -1, -1); - - SmsCbMessage cbMessage = msg.parseBroadcastSms(); - assertNull("expected null for unsupported charset", cbMessage); - } - - // VZW requirement is to discard message with unsupported charset. Verify that we return null - // for this unsupported character set. - public void testCmasUnsupportedCharSet2() throws Exception { - SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, - 67890, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH, - UserData.ENCODING_KOREAN, EXTREME_ALERT, -1, -1, -1, -1, -1); - - SmsCbMessage cbMessage = msg.parseBroadcastSms(); - assertNull("expected null for unsupported charset", cbMessage); - } - - // VZW requirement is to discard message without record type 0. The framework will decode it - // and the app will discard it. - public void testCmasNoRecordType0() throws Exception { - SmsMessage msg = createCmasSmsMessage( - SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, 1234, - BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH, - UserData.ENCODING_7BIT_ASCII, null, -1, -1, -1, -1, -1); - - SmsCbMessage cbMessage = msg.parseBroadcastSms(); - verifyCbValues(cbMessage); - assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, - cbMessage.getServiceCategory()); - assertEquals(1234, cbMessage.getSerialNumber()); - assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority()); - assertEquals("en", cbMessage.getLanguageCode()); - assertEquals(null, cbMessage.getMessageBody()); - assertEquals(true, cbMessage.isEmergencyMessage()); - assertEquals(true, cbMessage.isCmasMessage()); - SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo(); - assertEquals(SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, cmasInfo.getMessageClass()); - assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, cmasInfo.getCategory()); - assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, cmasInfo.getResponseType()); - assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN, cmasInfo.getSeverity()); - assertEquals(SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, cmasInfo.getUrgency()); - assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN, cmasInfo.getCertainty()); - } - - // Make sure we don't throw an exception if we feed completely random data to BearerStream. - public void testRandomBearerStreamData() { - Random r = new Random(54321); - for (int run = 0; run < 10000; run++) { - int len = r.nextInt(140); - byte[] data = new byte[len]; - for (int i = 0; i < len; i++) { - data[i] = (byte) r.nextInt(256); - } - // Log.d("CdmaSmsCbTest", "trying random bearer data run " + run + " length " + len); - try { - int category = 0x0ff0 + r.nextInt(32); // half CMAS, half non-CMAS - Parcel p = createBroadcastParcel(category); - SmsMessage msg = createMessageFromParcel(p, data); - SmsCbMessage cbMessage = msg.parseBroadcastSms(); - // with random input, cbMessage will almost always be null (log when it isn't) - if (cbMessage != null) { - Log.d("CdmaSmsCbTest", "success: " + cbMessage); - } - } catch (Exception e) { - Log.d("CdmaSmsCbTest", "exception thrown", e); - fail("Exception in decoder at run " + run + " length " + len + ": " + e); - } - } - } - - // Make sure we don't throw an exception if we put random data in the UserData subparam. - public void testRandomUserData() { - Random r = new Random(94040); - for (int run = 0; run < 10000; run++) { - int category = 0x0ff0 + r.nextInt(32); // half CMAS, half non-CMAS - Parcel p = createBroadcastParcel(category); - int len = r.nextInt(140); - // Log.d("CdmaSmsCbTest", "trying random user data run " + run + " length " + len); - - try { - BitwiseOutputStream bos = createBearerDataStream(r.nextInt(65536), r.nextInt(4), - r.nextInt(256)); - - bos.write(8, SUBPARAM_USER_DATA); - bos.write(8, len); - - for (int i = 0; i < len; i++) { - bos.write(8, r.nextInt(256)); - } - - SmsMessage msg = createMessageFromParcel(p, bos.toByteArray()); - SmsCbMessage cbMessage = msg.parseBroadcastSms(); - } catch (Exception e) { - Log.d("CdmaSmsCbTest", "exception thrown", e); - fail("Exception in decoder at run " + run + " length " + len + ": " + e); - } - } - } -} |