Merge "Add support for CMAS warning notifications over CDMA."
This commit is contained in:
@@ -596,8 +596,8 @@ public final class Telephony {
|
||||
* values:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
|
||||
* that make up the message.</li>
|
||||
* <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>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The extra values can be extracted using
|
||||
@@ -616,8 +616,8 @@ public final class Telephony {
|
||||
* values:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
|
||||
* that make up the message.</li>
|
||||
* <li><em>message</em> - An SmsCbMessage object containing the broadcast message
|
||||
* data, including ETWS or CMAS warning notification info if present.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The extra values can be extracted using
|
||||
@@ -630,6 +630,26 @@ public final class Telephony {
|
||||
public static final String SMS_EMERGENCY_CB_RECEIVED_ACTION =
|
||||
"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
|
||||
|
||||
@@ -77,6 +77,7 @@ public class BitwiseOutputStream {
|
||||
byte[] newBuf = new byte[(mPos + bits) >>> 2];
|
||||
System.arraycopy(mBuf, 0, newBuf, 0, mEnd >>> 3);
|
||||
mBuf = newBuf;
|
||||
mEnd = newBuf.length << 3;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -133,4 +133,25 @@ 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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
308
telephony/java/android/telephony/SmsCbCmasInfo.java
Normal file
308
telephony/java/android/telephony/SmsCbCmasInfo.java
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
206
telephony/java/android/telephony/SmsCbEtwsInfo.java
Normal file
206
telephony/java/android/telephony/SmsCbEtwsInfo.java
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
202
telephony/java/android/telephony/SmsCbLocation.java
Normal file
202
telephony/java/android/telephony/SmsCbLocation.java
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -16,118 +16,262 @@
|
||||
|
||||
package android.telephony;
|
||||
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.telephony.GsmAlphabet;
|
||||
import com.android.internal.telephony.IccUtils;
|
||||
import com.android.internal.telephony.gsm.SmsCbHeader;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Describes an SMS-CB message.
|
||||
* Parcelable object containing a received cell broadcast message. There are four different types
|
||||
* of Cell Broadcast messages:
|
||||
*
|
||||
* {@hide}
|
||||
* <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.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class SmsCbMessage {
|
||||
public class SmsCbMessage implements Parcelable {
|
||||
|
||||
/**
|
||||
* Cell wide immediate geographical scope
|
||||
*/
|
||||
protected static final String LOG_TAG = "SMSCB";
|
||||
|
||||
/** Cell wide geographical scope with immediate display (GSM/UMTS only). */
|
||||
public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
|
||||
|
||||
/**
|
||||
* PLMN wide geographical scope
|
||||
*/
|
||||
/** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
|
||||
public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
|
||||
|
||||
/**
|
||||
* Location / service area wide geographical scope
|
||||
*/
|
||||
/** Location / service area wide geographical scope (GSM/UMTS only). */
|
||||
public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
|
||||
|
||||
/**
|
||||
* Cell wide geographical scope
|
||||
*/
|
||||
/** Cell wide geographical scope (GSM/UMTS only). */
|
||||
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;
|
||||
|
||||
/**
|
||||
* Create an instance of this class from a received PDU
|
||||
* 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.
|
||||
*/
|
||||
private final int mSerialNumber;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private final SmsCbLocation mLocation;
|
||||
|
||||
/**
|
||||
* 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()}.
|
||||
*/
|
||||
private final int mServiceCategory;
|
||||
|
||||
/** Message language, as a two-character string, e.g. "en". */
|
||||
private final String mLanguage;
|
||||
|
||||
/** Message body, as a String. */
|
||||
private final String mBody;
|
||||
|
||||
/** Message priority (including emergency priority). */
|
||||
private final int mPriority;
|
||||
|
||||
/** ETWS warning notification information (ETWS warnings only). */
|
||||
private final SmsCbEtwsInfo mEtwsWarningInfo;
|
||||
|
||||
/** CMAS warning notification information (CMAS warnings only). */
|
||||
private final SmsCbCmasInfo mCmasWarningInfo;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
|
||||
case 'C':
|
||||
// unparcel CMAS warning information
|
||||
mEtwsWarningInfo = null;
|
||||
mCmasWarningInfo = new SmsCbCmasInfo(in);
|
||||
break;
|
||||
|
||||
default:
|
||||
mEtwsWarningInfo = null;
|
||||
mCmasWarningInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten this object into a Parcel.
|
||||
*
|
||||
* @param pdu PDU bytes
|
||||
* @return An instance of this class, or null if invalid pdu
|
||||
* @param dest The Parcel in which the object should be written.
|
||||
* @param flags Additional flags about how the object should be written (ignored).
|
||||
*/
|
||||
public static SmsCbMessage createFromPdu(byte[] pdu) {
|
||||
try {
|
||||
return new SmsCbMessage(pdu);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(LOG_TAG, "Failed parsing SMS-CB pdu", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String LOG_TAG = "SMSCB";
|
||||
|
||||
/**
|
||||
* Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
|
||||
*/
|
||||
private static final String[] LANGUAGE_CODES_GROUP_0 = {
|
||||
"de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu",
|
||||
"pl", null
|
||||
};
|
||||
|
||||
/**
|
||||
* Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
|
||||
*/
|
||||
private static final String[] LANGUAGE_CODES_GROUP_2 = {
|
||||
"cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null,
|
||||
null, null
|
||||
};
|
||||
|
||||
private static final char CARRIAGE_RETURN = 0x0d;
|
||||
|
||||
private static final int PDU_BODY_PAGE_LENGTH = 82;
|
||||
|
||||
private SmsCbHeader mHeader;
|
||||
|
||||
private String mLanguage;
|
||||
|
||||
private String mBody;
|
||||
|
||||
/** Timestamp of ETWS primary notification with security. */
|
||||
private long mPrimaryNotificationTimestamp;
|
||||
|
||||
/** 43 byte digital signature of ETWS primary notification with security. */
|
||||
private byte[] mPrimaryNotificationDigitalSignature;
|
||||
|
||||
private SmsCbMessage(byte[] pdu) throws IllegalArgumentException {
|
||||
mHeader = new SmsCbHeader(pdu);
|
||||
if (mHeader.format == SmsCbHeader.FORMAT_ETWS_PRIMARY) {
|
||||
mBody = "ETWS";
|
||||
// ETWS primary notification with security is 56 octets in length
|
||||
if (pdu.length >= SmsCbHeader.PDU_LENGTH_ETWS) {
|
||||
mPrimaryNotificationTimestamp = getTimestampMillis(pdu);
|
||||
mPrimaryNotificationDigitalSignature = new byte[43];
|
||||
// digital signature starts after 6 byte header and 7 byte timestamp
|
||||
System.arraycopy(pdu, 13, mPrimaryNotificationDigitalSignature, 0, 43);
|
||||
}
|
||||
@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);
|
||||
} else {
|
||||
parseBody(pdu);
|
||||
// no ETWS or CMAS warning information
|
||||
dest.writeInt('0');
|
||||
}
|
||||
}
|
||||
|
||||
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, one of
|
||||
* {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE},
|
||||
* {@link #GEOGRAPHICAL_SCOPE_PLMN_WIDE},
|
||||
* {@link #GEOGRAPHICAL_SCOPE_LA_WIDE},
|
||||
* {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE}
|
||||
* Return the geographical scope of this message (GSM/UMTS only).
|
||||
*
|
||||
* @return Geographical scope
|
||||
*/
|
||||
public int getGeographicalScope() {
|
||||
return mHeader.geographicalScope;
|
||||
return mGeographicalScope;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the 16-bit CDMA message identifier or GSM/UMTS serial number
|
||||
*/
|
||||
public int getSerialNumber() {
|
||||
return mSerialNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the geographical location code for duplicate message detection
|
||||
*/
|
||||
public SmsCbLocation getLocation() {
|
||||
return mLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the radio technology specific service category
|
||||
*/
|
||||
public int getServiceCategory() {
|
||||
return mServiceCategory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,311 +293,90 @@ public class SmsCbMessage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message identifier of this message (0-65535)
|
||||
*
|
||||
* @return Message identifier
|
||||
*/
|
||||
public int getMessageIdentifier() {
|
||||
return mHeader.messageIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message code of this message (0-1023)
|
||||
*
|
||||
* @return Message code
|
||||
*/
|
||||
public int getMessageCode() {
|
||||
return mHeader.messageCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the update number of this message (0-15)
|
||||
*
|
||||
* @return Update number
|
||||
*/
|
||||
public int getUpdateNumber() {
|
||||
return mHeader.updateNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the format of this message.
|
||||
* @return {@link SmsCbHeader#FORMAT_GSM}, {@link SmsCbHeader#FORMAT_UMTS}, or
|
||||
* {@link SmsCbHeader#FORMAT_ETWS_PRIMARY}
|
||||
* Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
|
||||
* @return an integer representing 3GPP or 3GPP2 message format
|
||||
*/
|
||||
public int getMessageFormat() {
|
||||
return mHeader.format;
|
||||
return mMessageFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* For ETWS primary notifications, return the emergency user alert flag.
|
||||
* @return true to notify terminal to activate emergency user alert; false otherwise
|
||||
* 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
|
||||
*/
|
||||
public boolean getEtwsEmergencyUserAlert() {
|
||||
return mHeader.etwsEmergencyUserAlert;
|
||||
public int getMessagePriority() {
|
||||
return mPriority;
|
||||
}
|
||||
|
||||
/**
|
||||
* For ETWS primary notifications, return the popup flag.
|
||||
* @return true to notify terminal to activate display popup; false otherwise
|
||||
*/
|
||||
public boolean getEtwsPopup() {
|
||||
return mHeader.etwsPopup;
|
||||
}
|
||||
|
||||
/**
|
||||
* For ETWS primary notifications, return the warning type.
|
||||
* @return a value such as {@link SmsCbConstants#ETWS_WARNING_TYPE_EARTHQUAKE}
|
||||
*/
|
||||
public int getEtwsWarningType() {
|
||||
return mHeader.etwsWarningType;
|
||||
}
|
||||
|
||||
/**
|
||||
* For ETWS primary notifications, return the Warning-Security-Information timestamp.
|
||||
* @return a timestamp in System.currentTimeMillis() format.
|
||||
*/
|
||||
public long getEtwsSecurityTimestamp() {
|
||||
return mPrimaryNotificationTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* For ETWS primary notifications, return the 43 byte digital signature.
|
||||
* @return a byte array containing a copy of the digital signature
|
||||
*/
|
||||
public byte[] getEtwsSecuritySignature() {
|
||||
return mPrimaryNotificationDigitalSignature.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and unpack the body text according to the encoding in the DCS.
|
||||
* After completing successfully this method will have assigned the body
|
||||
* text into mBody, and optionally the language code into mLanguage
|
||||
* 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.
|
||||
*
|
||||
* @param pdu The pdu
|
||||
* @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
|
||||
*/
|
||||
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);
|
||||
}
|
||||
public SmsCbEtwsInfo getEtwsWarningInfo() {
|
||||
return mEtwsWarningInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack body text from the pdu using the given encoding, position and
|
||||
* length within the pdu
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
* @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
|
||||
*/
|
||||
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;
|
||||
public SmsCbCmasInfo getCmasWarningInfo() {
|
||||
return mCmasWarningInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Return whether this message is an emergency (PWS) message type.
|
||||
* @return true if the message is a public warning notification; false otherwise
|
||||
*/
|
||||
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);
|
||||
public boolean isEmergencyMessage() {
|
||||
return mPriority == MESSAGE_PRIORITY_EMERGENCY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append text to the message body. This is used to concatenate multi-page GSM broadcasts.
|
||||
* @param body the text to append to this message
|
||||
* Return whether this message is an ETWS warning alert.
|
||||
* @return true if the message is an ETWS warning notification; false otherwise
|
||||
*/
|
||||
public void appendToBody(String body) {
|
||||
mBody = mBody + body;
|
||||
public boolean isEtwsMessage() {
|
||||
return mEtwsWarningInfo != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this message is a CMAS warning alert.
|
||||
* @return true if the message is a CMAS warning notification; false otherwise
|
||||
*/
|
||||
public boolean isCmasMessage() {
|
||||
return mCmasWarningInfo != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SmsCbMessage{" + mHeader.toString() + ", language=" + mLanguage +
|
||||
", body=\"" + mBody + "\"}";
|
||||
return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber="
|
||||
+ mSerialNumber + ", location=" + mLocation + ", serviceCategory="
|
||||
+ mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
|
||||
+ ", priority=" + mPriority
|
||||
+ (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
|
||||
+ (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
210
telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
Normal file
210
telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -39,6 +39,7 @@ 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;
|
||||
@@ -339,7 +340,7 @@ public abstract class SMSDispatcher extends Handler {
|
||||
* @param intent intent to broadcast
|
||||
* @param permission Receivers are required to have this permission
|
||||
*/
|
||||
void dispatch(Intent intent, String permission) {
|
||||
public void dispatch(Intent intent, String permission) {
|
||||
// Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
|
||||
// receivers time to take their own wake locks.
|
||||
mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
|
||||
@@ -1078,16 +1079,16 @@ public abstract class SMSDispatcher extends Handler {
|
||||
}
|
||||
};
|
||||
|
||||
protected void dispatchBroadcastPdus(byte[][] pdus, boolean isEmergencyMessage) {
|
||||
if (isEmergencyMessage) {
|
||||
protected void dispatchBroadcastMessage(SmsCbMessage message) {
|
||||
if (message.isEmergencyMessage()) {
|
||||
Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
|
||||
intent.putExtra("pdus", pdus);
|
||||
Log.d(TAG, "Dispatching " + pdus.length + " emergency SMS CB pdus");
|
||||
intent.putExtra("message", message);
|
||||
Log.d(TAG, "Dispatching emergency SMS CB");
|
||||
dispatch(intent, RECEIVE_EMERGENCY_BROADCAST_PERMISSION);
|
||||
} else {
|
||||
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
|
||||
intent.putExtra("pdus", pdus);
|
||||
Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus");
|
||||
intent.putExtra("message", message);
|
||||
Log.d(TAG, "Dispatching SMS CB");
|
||||
dispatch(intent, RECEIVE_SMS_PERMISSION);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,10 @@ 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.telephony.cdma.CdmaSmsCbProgramData;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.telephony.CommandsInterface;
|
||||
@@ -50,6 +52,7 @@ import com.android.internal.util.HexDump;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.res.Resources;
|
||||
|
||||
@@ -97,6 +100,24 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch service category program data to the CellBroadcastReceiver app, which filters
|
||||
* the broadcast alerts to display.
|
||||
* @param sms the SMS message containing one or more
|
||||
* {@link android.telephony.cdma.CdmaSmsCbProgramData} objects.
|
||||
*/
|
||||
private void handleServiceCategoryProgramData(SmsMessage sms) {
|
||||
List<CdmaSmsCbProgramData> programDataList = sms.getSmsCbProgramData();
|
||||
if (programDataList == null) {
|
||||
Log.e(TAG, "handleServiceCategoryProgramData: program data list is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION);
|
||||
intent.putExtra("program_data_list", (CdmaSmsCbProgramData[]) programDataList.toArray());
|
||||
dispatch(intent, RECEIVE_SMS_PERMISSION);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public int dispatchMessage(SmsMessageBase smsb) {
|
||||
@@ -119,8 +140,19 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
|
||||
return Intents.RESULT_SMS_HANDLED;
|
||||
}
|
||||
|
||||
// See if we have a network duplicate SMS.
|
||||
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.
|
||||
mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
|
||||
if (mLastAcknowledgedSmsFingerprint != null &&
|
||||
Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
|
||||
@@ -149,6 +181,9 @@ 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");
|
||||
|
||||
@@ -19,7 +19,11 @@ 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.telephony.cdma.CdmaSmsCbProgramData;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.telephony.IccUtils;
|
||||
import com.android.internal.telephony.SmsHeader;
|
||||
import com.android.internal.telephony.SmsMessageBase;
|
||||
@@ -38,6 +42,9 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static android.telephony.SmsMessage.MessageClass;
|
||||
|
||||
/**
|
||||
* TODO(cleanup): these constants are disturbing... are they not just
|
||||
@@ -47,12 +54,6 @@ import java.io.IOException;
|
||||
* 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
|
||||
@@ -192,16 +193,17 @@ public class SmsMessage extends SmsMessageBase {
|
||||
|
||||
// bearer data
|
||||
countInt = p.readInt(); //p_cur->uBearerDataLen
|
||||
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
|
||||
if (countInt < 0) {
|
||||
countInt = 0;
|
||||
}
|
||||
|
||||
data = new byte[countInt];
|
||||
for (int index=0; index < countInt; index++) {
|
||||
data[index] = p.readByte();
|
||||
}
|
||||
// BD gets further decoded when accessed in SMSDispatcher
|
||||
env.bearerData = data;
|
||||
|
||||
// link the the filled objects to the SMS
|
||||
env.origAddress = addr;
|
||||
env.origSubaddress = subaddr;
|
||||
@@ -731,6 +733,29 @@ 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}
|
||||
*/
|
||||
@@ -961,4 +986,12 @@ public class SmsMessage extends SmsMessageBase {
|
||||
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of service category program data, if present.
|
||||
* @return a list of CdmaSmsCbProgramData objects, or null if not present
|
||||
*/
|
||||
List<CdmaSmsCbProgramData> getSmsCbProgramData() {
|
||||
return mBearerData.serviceCategoryProgramData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,28 +16,29 @@
|
||||
|
||||
package com.android.internal.telephony.cdma.sms;
|
||||
|
||||
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.content.res.Resources;
|
||||
import android.telephony.SmsCbCmasInfo;
|
||||
import android.telephony.SmsCbMessage;
|
||||
import android.telephony.SmsMessage;
|
||||
import android.telephony.cdma.CdmaSmsCbProgramData;
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
|
||||
import android.telephony.SmsMessage;
|
||||
|
||||
import android.text.format.Time;
|
||||
|
||||
import com.android.internal.telephony.IccUtils;
|
||||
import com.android.internal.telephony.GsmAlphabet;
|
||||
import com.android.internal.telephony.IccUtils;
|
||||
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 android.content.res.Resources;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
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.
|
||||
@@ -68,8 +69,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;
|
||||
@@ -339,12 +340,68 @@ 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();
|
||||
@@ -916,6 +973,9 @@ 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) {
|
||||
@@ -926,11 +986,12 @@ public final class BearerData {
|
||||
private static String decodeUtf16(byte[] data, int offset, int numFields)
|
||||
throws CodingException
|
||||
{
|
||||
// Start reading from the next 16-bit aligned boundary after offset.
|
||||
int padding = offset % 2;
|
||||
numFields -= (offset + padding) / 2;
|
||||
int byteCount = numFields * 2;
|
||||
if (byteCount < 0 || (byteCount + offset) > data.length) {
|
||||
throw new CodingException("UTF-16 decode failed: offset or length out of range");
|
||||
}
|
||||
try {
|
||||
return new String(data, offset, numFields * 2, "utf-16be");
|
||||
return new String(data, offset, byteCount, "utf-16be");
|
||||
} catch (java.io.UnsupportedEncodingException ex) {
|
||||
throw new CodingException("UTF-16 decode failed: " + ex);
|
||||
}
|
||||
@@ -988,8 +1049,11 @@ 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 - offset, "ISO-8859-1");
|
||||
return new String(data, offset, numFields, "ISO-8859-1");
|
||||
} catch (java.io.UnsupportedEncodingException ex) {
|
||||
throw new CodingException("ISO-8859-1 decode failed: " + ex);
|
||||
}
|
||||
@@ -1032,6 +1096,7 @@ 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);
|
||||
@@ -1114,8 +1179,9 @@ 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;
|
||||
if ((dataLen > 14) || (dataLen < numFields)) {
|
||||
throw new CodingException("IS-91 voicemail status decoding failed");
|
||||
// dataLen may be > 14 characters due to octet padding
|
||||
if ((numFields > 14) || (dataLen < numFields)) {
|
||||
throw new CodingException("IS-91 short message decoding failed");
|
||||
}
|
||||
StringBuffer strbuf = new StringBuffer(dataLen);
|
||||
for (int i = 0; i < numFields; i++) {
|
||||
@@ -1539,7 +1605,7 @@ public final class BearerData {
|
||||
bData.userResponseCode = inStream.read(8);
|
||||
}
|
||||
if ((! decodeSuccess) || (paramBits > 0)) {
|
||||
Log.d(LOG_TAG, "USER_REPONSE_CODE decode " +
|
||||
Log.d(LOG_TAG, "USER_RESPONSE_CODE decode " +
|
||||
(decodeSuccess ? "succeeded" : "failed") +
|
||||
" (extra bits = " + paramBits + ")");
|
||||
}
|
||||
@@ -1548,27 +1614,240 @@ 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) {
|
||||
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);
|
||||
@@ -1624,6 +1903,9 @@ 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 + ")");
|
||||
@@ -1634,7 +1916,10 @@ public final class BearerData {
|
||||
throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
|
||||
}
|
||||
if (bData.userData != null) {
|
||||
if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
|
||||
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 ((foundSubparamMask ^
|
||||
(1 << SUBPARAM_MESSAGE_IDENTIFIER) ^
|
||||
(1 << SUBPARAM_USER_DATA))
|
||||
|
||||
@@ -37,6 +37,7 @@ 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
|
||||
@@ -46,14 +47,17 @@ 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;
|
||||
|
||||
// ServiceCategories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
|
||||
//static public final int SERVICECATEGORY_EMERGENCY = 0x0010;
|
||||
// Service Categories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
|
||||
// static final int SERVICE_CATEGORY_EMERGENCY = 0x0001;
|
||||
//...
|
||||
|
||||
/**
|
||||
* maximum lengths for fields as defined in ril_cdma_sms.h
|
||||
*/
|
||||
static public final int SMS_BEARER_DATA_MAX = 255;
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Provides the type of a SMS message like point to point, broadcast or acknowledge
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -26,6 +26,7 @@ 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;
|
||||
@@ -318,24 +319,18 @@ public final class GsmSMSDispatcher extends SMSDispatcher {
|
||||
* concatenated message
|
||||
*/
|
||||
private static final class SmsCbConcatInfo {
|
||||
|
||||
private final SmsCbHeader mHeader;
|
||||
private final SmsCbLocation mLocation;
|
||||
|
||||
private final String mPlmn;
|
||||
|
||||
private final int mLac;
|
||||
|
||||
private final int mCid;
|
||||
|
||||
public SmsCbConcatInfo(SmsCbHeader header, String plmn, int lac, int cid) {
|
||||
public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
|
||||
mHeader = header;
|
||||
mPlmn = plmn;
|
||||
mLac = lac;
|
||||
mCid = cid;
|
||||
mLocation = location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mHeader.messageIdentifier * 31 + mHeader.updateNumber;
|
||||
return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -343,49 +338,28 @@ public final class GsmSMSDispatcher extends SMSDispatcher {
|
||||
if (obj instanceof SmsCbConcatInfo) {
|
||||
SmsCbConcatInfo other = (SmsCbConcatInfo)obj;
|
||||
|
||||
// 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);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this concatenation info matches the given location. The
|
||||
* granularity of the match depends on the geographical scope.
|
||||
* 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.
|
||||
*
|
||||
* @param plmn PLMN
|
||||
* @param lac Location area code
|
||||
* @param cid Cell ID
|
||||
* @return true if matching, false otherwise
|
||||
* @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
|
||||
*/
|
||||
public boolean matchesLocation(String plmn, int lac, int 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;
|
||||
return mLocation.isInLocationArea(plmn, lac, cid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,24 +395,42 @@ 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;
|
||||
if (header.nrOfPages > 1) {
|
||||
int pageCount = header.getNumberOfPages();
|
||||
if (pageCount > 1) {
|
||||
// Multi-page message
|
||||
SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, plmn, lac, cid);
|
||||
SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
|
||||
|
||||
// Try to find other pages of the same message
|
||||
pdus = mSmsCbPageMap.get(concatInfo);
|
||||
|
||||
if (pdus == null) {
|
||||
// This it the first page of this message, make room for all
|
||||
// This is the first page of this message, make room for all
|
||||
// pages and keep until complete
|
||||
pdus = new byte[header.nrOfPages][];
|
||||
pdus = new byte[pageCount][];
|
||||
|
||||
mSmsCbPageMap.put(concatInfo, pdus);
|
||||
}
|
||||
|
||||
// Page parameter is one-based
|
||||
pdus[header.pageIndex - 1] = receivedPdu;
|
||||
pdus[header.getPageIndex() - 1] = receivedPdu;
|
||||
|
||||
for (int i = 0; i < pdus.length; i++) {
|
||||
if (pdus[i] == null) {
|
||||
@@ -455,8 +447,8 @@ public final class GsmSMSDispatcher extends SMSDispatcher {
|
||||
pdus[0] = receivedPdu;
|
||||
}
|
||||
|
||||
boolean isEmergencyMessage = SmsCbHeader.isEmergencyMessage(header.messageIdentifier);
|
||||
dispatchBroadcastPdus(pdus, isEmergencyMessage);
|
||||
SmsCbMessage message = GsmSmsCbMessage.createSmsCbMessage(header, location, pdus);
|
||||
dispatchBroadcastMessage(message);
|
||||
|
||||
// Remove messages that are out of scope to prevent the map from
|
||||
// growing indefinitely, containing incomplete messages that were
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -14,14 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.telephony;
|
||||
package com.android.internal.telephony.gsm;
|
||||
|
||||
/**
|
||||
* Constants used in SMS Cell Broadcast messages.
|
||||
* 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.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
public interface SmsCbConstants {
|
||||
public class SmsCbConstants {
|
||||
|
||||
/** Private constructor for utility class. */
|
||||
private SmsCbConstants() { }
|
||||
|
||||
/** Start of PWS Message Identifier range (includes ETWS and CMAS). */
|
||||
public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER = 0x1100;
|
||||
|
||||
@@ -94,11 +102,11 @@ public interface SmsCbConstants {
|
||||
/** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
|
||||
public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER = 0x18FF;
|
||||
|
||||
/** ETWS message code flag to activate the popup display. */
|
||||
public static final int MESSAGE_CODE_ETWS_ACTIVATE_POPUP = 0x100;
|
||||
/** ETWS 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 emergency user alert. */
|
||||
public static final int MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT = 0x200;
|
||||
/** ETWS serial number flag to activate the emergency user alert. */
|
||||
public static final int SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT = 0x2000;
|
||||
|
||||
/** ETWS warning type value for earthquake. */
|
||||
public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
|
||||
@@ -16,28 +16,42 @@
|
||||
|
||||
package com.android.internal.telephony.gsm;
|
||||
|
||||
import android.telephony.SmsCbConstants;
|
||||
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 {
|
||||
|
||||
public class SmsCbHeader implements SmsCbConstants {
|
||||
/**
|
||||
* Length of SMS-CB header
|
||||
*/
|
||||
public static final int PDU_HEADER_LENGTH = 6;
|
||||
static final int PDU_HEADER_LENGTH = 6;
|
||||
|
||||
/**
|
||||
* GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
|
||||
*/
|
||||
public static final int FORMAT_GSM = 1;
|
||||
static final int FORMAT_GSM = 1;
|
||||
|
||||
/**
|
||||
* UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2
|
||||
*/
|
||||
public static final int FORMAT_UMTS = 2;
|
||||
static final int FORMAT_UMTS = 2;
|
||||
|
||||
/**
|
||||
* GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
|
||||
*/
|
||||
public static final int FORMAT_ETWS_PRIMARY = 3;
|
||||
static final int FORMAT_ETWS_PRIMARY = 3;
|
||||
|
||||
/**
|
||||
* Message type value as defined in 3gpp TS 25.324, section 11.1.
|
||||
@@ -47,34 +61,34 @@ public class SmsCbHeader implements SmsCbConstants {
|
||||
/**
|
||||
* Length of GSM pdus
|
||||
*/
|
||||
public static final int PDU_LENGTH_GSM = 88;
|
||||
private static final int PDU_LENGTH_GSM = 88;
|
||||
|
||||
/**
|
||||
* Maximum length of ETWS primary message GSM pdus
|
||||
*/
|
||||
public static final int PDU_LENGTH_ETWS = 56;
|
||||
private 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;
|
||||
|
||||
public final boolean etwsPopup;
|
||||
|
||||
public final int etwsWarningType;
|
||||
/** CMAS warning notification info. */
|
||||
private final SmsCbCmasInfo mCmasInfo;
|
||||
|
||||
public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
|
||||
if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
|
||||
@@ -83,22 +97,31 @@ public class SmsCbHeader implements SmsCbConstants {
|
||||
|
||||
if (pdu.length <= PDU_LENGTH_ETWS) {
|
||||
format = FORMAT_ETWS_PRIMARY;
|
||||
geographicalScope = -1; //not applicable
|
||||
messageCode = -1;
|
||||
updateNumber = -1;
|
||||
geographicalScope = (pdu[0] & 0xc0) >> 6;
|
||||
serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
|
||||
messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
|
||||
dataCodingScheme = -1;
|
||||
pageIndex = -1;
|
||||
nrOfPages = -1;
|
||||
etwsEmergencyUserAlert = (pdu[4] & 0x1) != 0;
|
||||
etwsPopup = (pdu[5] & 0x80) != 0;
|
||||
etwsWarningType = (pdu[4] & 0xfe) >> 1;
|
||||
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
|
||||
} else if (pdu.length <= PDU_LENGTH_GSM) {
|
||||
// GSM pdus are no more than 88 bytes
|
||||
format = FORMAT_GSM;
|
||||
geographicalScope = (pdu[0] & 0xc0) >> 6;
|
||||
messageCode = ((pdu[0] & 0x3f) << 4) | ((pdu[1] & 0xf0) >> 4);
|
||||
updateNumber = pdu[1] & 0x0f;
|
||||
serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
|
||||
messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
|
||||
dataCodingScheme = pdu[4] & 0xff;
|
||||
|
||||
@@ -113,9 +136,6 @@ public class SmsCbHeader implements SmsCbConstants {
|
||||
|
||||
this.pageIndex = pageIndex;
|
||||
this.nrOfPages = nrOfPages;
|
||||
etwsEmergencyUserAlert = false;
|
||||
etwsPopup = false;
|
||||
etwsWarningType = -1;
|
||||
} else {
|
||||
// UMTS pdus are always at least 90 bytes since the payload includes
|
||||
// a number-of-pages octet and also one length octet per page
|
||||
@@ -129,8 +149,7 @@ public class SmsCbHeader implements SmsCbConstants {
|
||||
|
||||
messageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
|
||||
geographicalScope = (pdu[3] & 0xc0) >> 6;
|
||||
messageCode = ((pdu[3] & 0x3f) << 4) | ((pdu[4] & 0xf0) >> 4);
|
||||
updateNumber = pdu[4] & 0x0f;
|
||||
serialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
|
||||
dataCodingScheme = pdu[5] & 0xff;
|
||||
|
||||
// We will always consider a UMTS message as having one single page
|
||||
@@ -138,75 +157,251 @@ public class SmsCbHeader implements SmsCbConstants {
|
||||
// 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
|
||||
*/
|
||||
boolean isEtwsPrimaryNotification() {
|
||||
return format == FORMAT_ETWS_PRIMARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this broadcast is in UMTS format.
|
||||
* @return true if this message is in UMTS format; false otherwise
|
||||
*/
|
||||
boolean isUmtsFormat() {
|
||||
return format == FORMAT_UMTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this message is a CMAS emergency message type.
|
||||
* @return true if this 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the popup alert flag is set for an ETWS warning notification.
|
||||
* This method assumes that the message ID has already been checked for ETWS type.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the emergency user alert flag is set for an ETWS warning notification.
|
||||
* This method assumes that the message ID has already been checked for ETWS type.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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}
|
||||
*/
|
||||
public static boolean isEmergencyMessage(int id) {
|
||||
return id >= MESSAGE_ID_PWS_FIRST_IDENTIFIER && id <= MESSAGE_ID_PWS_LAST_IDENTIFIER;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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}
|
||||
*/
|
||||
public static boolean isEtwsMessage(int id) {
|
||||
return (id & MESSAGE_ID_ETWS_TYPE_MASK) == MESSAGE_ID_ETWS_TYPE;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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}
|
||||
*/
|
||||
public static boolean isCmasMessage(int id) {
|
||||
return id >= MESSAGE_ID_CMAS_FIRST_IDENTIFIER && id <= MESSAGE_ID_CMAS_LAST_IDENTIFIER;
|
||||
}
|
||||
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;
|
||||
|
||||
/**
|
||||
* Return whether the specified message code indicates an ETWS popup alert.
|
||||
* This method is static and takes an argument so that it can be used by
|
||||
* CellBroadcastReceiver, which stores message codes in SQLite rather than PDU.
|
||||
* This method assumes that the message ID has already been checked for ETWS type.
|
||||
*
|
||||
* @param messageCode the message code to check
|
||||
* @return true if the message code indicates a popup alert should be displayed
|
||||
*/
|
||||
public static boolean isEtwsPopupAlert(int messageCode) {
|
||||
return (messageCode & MESSAGE_CODE_ETWS_ACTIVATE_POPUP) != 0;
|
||||
}
|
||||
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;
|
||||
|
||||
/**
|
||||
* Return whether the specified message code indicates an ETWS emergency user alert.
|
||||
* This method is static and takes an argument so that it can be used by
|
||||
* CellBroadcastReceiver, which stores message codes in SQLite rather than PDU.
|
||||
* This method assumes that the message ID has already been checked for ETWS type.
|
||||
*
|
||||
* @param messageCode the message code to check
|
||||
* @return true if the message code indicates an emergency user alert
|
||||
*/
|
||||
public static boolean isEtwsEmergencyUserAlert(int messageCode) {
|
||||
return (messageCode & MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT) != 0;
|
||||
default:
|
||||
return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SmsCbHeader{GS=" + geographicalScope + ", messageCode=0x" +
|
||||
Integer.toHexString(messageCode) + ", updateNumber=" + updateNumber +
|
||||
return "SmsCbHeader{GS=" + geographicalScope + ", serialNumber=0x" +
|
||||
Integer.toHexString(serialNumber) +
|
||||
", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) +
|
||||
", DCS=0x" + Integer.toHexString(dataCodingScheme) +
|
||||
", page " + pageIndex + " of " + nrOfPages + '}';
|
||||
|
||||
@@ -0,0 +1,708 @@
|
||||
/*
|
||||
* 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.telephony.cdma.CdmaSmsCbProgramData;
|
||||
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.List;
|
||||
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;
|
||||
private static final byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12;
|
||||
|
||||
/**
|
||||
* 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 < 1000; 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 < 1000; 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a Parcel for incoming Service Category Program Data teleservice. The caller will
|
||||
* write the bearer data and then convert it to an SmsMessage.
|
||||
* @return the initialized Parcel
|
||||
*/
|
||||
private static Parcel createServiceCategoryProgramDataParcel() {
|
||||
Parcel p = Parcel.obtain();
|
||||
|
||||
p.writeInt(SmsEnvelope.TELESERVICE_SCPT);
|
||||
p.writeByte((byte) 0); // non-zero for MESSAGE_TYPE_BROADCAST
|
||||
p.writeInt(0);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
private static final String CAT_EXTREME_THREAT = "Extreme Threat to Life and Property";
|
||||
private static final String CAT_SEVERE_THREAT = "Severe Threat to Life and Property";
|
||||
private static final String CAT_AMBER_ALERTS = "AMBER Alerts";
|
||||
|
||||
public void testServiceCategoryProgramDataAddCategory() throws Exception {
|
||||
Parcel p = createServiceCategoryProgramDataParcel();
|
||||
BitwiseOutputStream bos = createBearerDataStream(123, -1, -1);
|
||||
|
||||
int categoryNameLength = CAT_EXTREME_THREAT.length();
|
||||
int subparamLengthBits = (53 + (categoryNameLength * 7));
|
||||
int subparamLengthBytes = (subparamLengthBits + 7) / 8;
|
||||
int subparamPadBits = (subparamLengthBytes * 8) - subparamLengthBits;
|
||||
|
||||
bos.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA);
|
||||
bos.write(8, subparamLengthBytes);
|
||||
bos.write(5, UserData.ENCODING_7BIT_ASCII);
|
||||
|
||||
bos.write(4, CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY);
|
||||
bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT >>> 8));
|
||||
bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT & 0xff));
|
||||
bos.write(8, BearerData.LANGUAGE_ENGLISH);
|
||||
bos.write(8, 100); // max messages
|
||||
bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_DEFAULT_ALERT);
|
||||
|
||||
bos.write(8, categoryNameLength);
|
||||
for (int i = 0; i < categoryNameLength; i++) {
|
||||
bos.write(7, CAT_EXTREME_THREAT.charAt(i));
|
||||
}
|
||||
bos.write(subparamPadBits, 0);
|
||||
|
||||
SmsMessage msg = createMessageFromParcel(p, bos.toByteArray());
|
||||
assertNotNull(msg);
|
||||
msg.parseSms();
|
||||
List<CdmaSmsCbProgramData> programDataList = msg.getSmsCbProgramData();
|
||||
assertNotNull(programDataList);
|
||||
assertEquals(1, programDataList.size());
|
||||
CdmaSmsCbProgramData programData = programDataList.get(0);
|
||||
assertEquals(CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY, programData.getOperation());
|
||||
assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, programData.getCategory());
|
||||
assertEquals(CAT_EXTREME_THREAT, programData.getCategoryName());
|
||||
assertEquals("en", programData.getLanguageCode());
|
||||
assertEquals(100, programData.getMaxMessages());
|
||||
assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_DEFAULT_ALERT, programData.getAlertOption());
|
||||
}
|
||||
|
||||
public void testServiceCategoryProgramDataDeleteTwoCategories() throws Exception {
|
||||
Parcel p = createServiceCategoryProgramDataParcel();
|
||||
BitwiseOutputStream bos = createBearerDataStream(456, -1, -1);
|
||||
|
||||
int category1NameLength = CAT_SEVERE_THREAT.length();
|
||||
int category2NameLength = CAT_AMBER_ALERTS.length();
|
||||
|
||||
int subparamLengthBits = (101 + (category1NameLength * 7) + (category2NameLength * 7));
|
||||
int subparamLengthBytes = (subparamLengthBits + 7) / 8;
|
||||
int subparamPadBits = (subparamLengthBytes * 8) - subparamLengthBits;
|
||||
|
||||
bos.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA);
|
||||
bos.write(8, subparamLengthBytes);
|
||||
bos.write(5, UserData.ENCODING_7BIT_ASCII);
|
||||
|
||||
bos.write(4, CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY);
|
||||
bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT >>> 8));
|
||||
bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT & 0xff));
|
||||
bos.write(8, BearerData.LANGUAGE_ENGLISH);
|
||||
bos.write(8, 0); // max messages
|
||||
bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT);
|
||||
|
||||
bos.write(8, category1NameLength);
|
||||
for (int i = 0; i < category1NameLength; i++) {
|
||||
bos.write(7, CAT_SEVERE_THREAT.charAt(i));
|
||||
}
|
||||
|
||||
bos.write(4, CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY);
|
||||
bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY >>> 8));
|
||||
bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY & 0xff));
|
||||
bos.write(8, BearerData.LANGUAGE_ENGLISH);
|
||||
bos.write(8, 0); // max messages
|
||||
bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT);
|
||||
|
||||
bos.write(8, category2NameLength);
|
||||
for (int i = 0; i < category2NameLength; i++) {
|
||||
bos.write(7, CAT_AMBER_ALERTS.charAt(i));
|
||||
}
|
||||
|
||||
bos.write(subparamPadBits, 0);
|
||||
|
||||
SmsMessage msg = createMessageFromParcel(p, bos.toByteArray());
|
||||
assertNotNull(msg);
|
||||
msg.parseSms();
|
||||
List<CdmaSmsCbProgramData> programDataList = msg.getSmsCbProgramData();
|
||||
assertNotNull(programDataList);
|
||||
assertEquals(2, programDataList.size());
|
||||
|
||||
CdmaSmsCbProgramData programData = programDataList.get(0);
|
||||
assertEquals(CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY, programData.getOperation());
|
||||
assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, programData.getCategory());
|
||||
assertEquals(CAT_SEVERE_THREAT, programData.getCategoryName());
|
||||
assertEquals("en", programData.getLanguageCode());
|
||||
assertEquals(0, programData.getMaxMessages());
|
||||
assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT, programData.getAlertOption());
|
||||
|
||||
programData = programDataList.get(1);
|
||||
assertEquals(CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY, programData.getOperation());
|
||||
assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY,
|
||||
programData.getCategory());
|
||||
assertEquals(CAT_AMBER_ALERTS, programData.getCategoryName());
|
||||
assertEquals("en", programData.getLanguageCode());
|
||||
assertEquals(0, programData.getMaxMessages());
|
||||
assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT, programData.getAlertOption());
|
||||
}
|
||||
}
|
||||
@@ -14,34 +14,54 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.telephony;
|
||||
package com.android.internal.telephony.gsm;
|
||||
|
||||
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 void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) {
|
||||
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) {
|
||||
pdu[0] = b;
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected geographical scope decoded", expectedGs, msg
|
||||
.getGeographicalScope());
|
||||
}
|
||||
|
||||
public void testCreateNullPdu() {
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(null);
|
||||
|
||||
SmsCbMessage msg = createFromPdu(null);
|
||||
assertNull("createFromPdu(byte[] with null pdu should return null", msg);
|
||||
}
|
||||
|
||||
public void testCreateTooShortPdu() {
|
||||
byte[] pdu = new byte[4];
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertNull("createFromPdu(byte[] with too short pdu should return null", msg);
|
||||
}
|
||||
@@ -94,7 +114,7 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
(byte)0x34
|
||||
};
|
||||
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected geographical scope decoded",
|
||||
SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE, msg.getGeographicalScope());
|
||||
@@ -116,7 +136,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 = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected 7-bit string decoded",
|
||||
"A GSM default alphabet message with carriage return padding",
|
||||
@@ -146,7 +166,7 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
|
||||
(byte)0x34
|
||||
};
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected 7-bit string decoded",
|
||||
"A GSM default alphabet message with carriage return padding",
|
||||
@@ -193,7 +213,7 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
|
||||
(byte)0x0A
|
||||
};
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected multipage 7-bit string decoded",
|
||||
"First page+Second page",
|
||||
@@ -216,7 +236,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 = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals(
|
||||
"Unexpected 7-bit string decoded",
|
||||
@@ -248,7 +268,7 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
|
||||
(byte)0x52
|
||||
};
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals(
|
||||
"Unexpected 7-bit string decoded",
|
||||
@@ -273,7 +293,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 = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected 7-bit string decoded",
|
||||
"A GSM default alphabet message with carriage return padding",
|
||||
@@ -298,7 +318,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 = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected 7-bit string decoded",
|
||||
"A GSM default alphabet message with carriage return padding",
|
||||
@@ -330,7 +350,7 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
|
||||
(byte)0x37
|
||||
};
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected 7-bit string decoded",
|
||||
"A GSM default alphabet message with carriage return padding",
|
||||
@@ -355,7 +375,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 = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("8-bit message body should be empty", "", msg.getMessageBody());
|
||||
}
|
||||
@@ -376,7 +396,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 = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected 7-bit string decoded",
|
||||
"A UCS2 message containing a \u0434 character", msg.getMessageBody());
|
||||
@@ -405,7 +425,7 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
|
||||
(byte)0x4E
|
||||
};
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected 7-bit string decoded",
|
||||
"A UCS2 message containing a \u0434 character", msg.getMessageBody());
|
||||
@@ -451,7 +471,7 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
|
||||
(byte)0x06
|
||||
};
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected multipage UCS2 string decoded",
|
||||
"AAABBB", msg.getMessageBody());
|
||||
@@ -473,7 +493,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 = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected 7-bit string decoded",
|
||||
"A UCS2 message containing a \u0434 character", msg.getMessageBody());
|
||||
@@ -504,7 +524,7 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
|
||||
(byte)0x50
|
||||
};
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected 7-bit string decoded",
|
||||
"A UCS2 message containing a \u0434 character", msg.getMessageBody());
|
||||
@@ -529,9 +549,9 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
(byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
|
||||
};
|
||||
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected message identifier decoded", 12345, msg.getMessageIdentifier());
|
||||
assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory());
|
||||
}
|
||||
|
||||
public void testGetMessageIdentifierUmts() {
|
||||
@@ -558,9 +578,9 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
(byte)0x34
|
||||
};
|
||||
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
|
||||
assertEquals("Unexpected message identifier decoded", 12345, msg.getMessageIdentifier());
|
||||
assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory());
|
||||
}
|
||||
|
||||
public void testGetMessageCode() {
|
||||
@@ -580,9 +600,10 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
(byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
|
||||
};
|
||||
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4;
|
||||
|
||||
assertEquals("Unexpected message code decoded", 682, msg.getMessageCode());
|
||||
assertEquals("Unexpected message code decoded", 682, messageCode);
|
||||
}
|
||||
|
||||
public void testGetMessageCodeUmts() {
|
||||
@@ -609,9 +630,10 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
(byte)0x34
|
||||
};
|
||||
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4;
|
||||
|
||||
assertEquals("Unexpected message code decoded", 682, msg.getMessageCode());
|
||||
assertEquals("Unexpected message code decoded", 682, messageCode);
|
||||
}
|
||||
|
||||
public void testGetUpdateNumber() {
|
||||
@@ -631,9 +653,10 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
(byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
|
||||
};
|
||||
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
int updateNumber = msg.getSerialNumber() & 0x000f;
|
||||
|
||||
assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber());
|
||||
assertEquals("Unexpected update number decoded", 5, updateNumber);
|
||||
}
|
||||
|
||||
public void testGetUpdateNumberUmts() {
|
||||
@@ -660,9 +683,10 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
(byte)0x34
|
||||
};
|
||||
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
|
||||
SmsCbMessage msg = createFromPdu(pdu);
|
||||
int updateNumber = msg.getSerialNumber() & 0x000f;
|
||||
|
||||
assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber());
|
||||
assertEquals("Unexpected update number decoded", 5, updateNumber);
|
||||
}
|
||||
|
||||
/* ETWS Test message including header */
|
||||
@@ -684,29 +708,51 @@ public class GsmSmsCbTest extends AndroidTestCase {
|
||||
// FIXME: add example of ETWS primary notification PDU
|
||||
|
||||
public void testEtwsMessageNormal() {
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageNormal);
|
||||
Log.d("GsmSmsCbTest", msg.toString());
|
||||
SmsCbMessage msg = createFromPdu(etwsMessageNormal);
|
||||
Log.d(TAG, msg.toString());
|
||||
assertEquals("GS mismatch", 0, msg.getGeographicalScope());
|
||||
assertEquals("message code mismatch", 0, msg.getMessageCode());
|
||||
assertEquals("update number mismatch", 0, msg.getUpdateNumber());
|
||||
assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier());
|
||||
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());
|
||||
}
|
||||
|
||||
public void testEtwsMessageCancel() {
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageCancel);
|
||||
Log.d("GsmSmsCbTest", msg.toString());
|
||||
SmsCbMessage msg = createFromPdu(etwsMessageCancel);
|
||||
Log.d(TAG, msg.toString());
|
||||
assertEquals("GS mismatch", 0, msg.getGeographicalScope());
|
||||
assertEquals("message code mismatch", 0, msg.getMessageCode());
|
||||
assertEquals("update number mismatch", 0, msg.getUpdateNumber());
|
||||
assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier());
|
||||
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());
|
||||
}
|
||||
|
||||
public void testEtwsMessageTest() {
|
||||
SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageTest);
|
||||
Log.d("GsmSmsCbTest", msg.toString());
|
||||
SmsCbMessage msg = createFromPdu(etwsMessageTest);
|
||||
Log.d(TAG, msg.toString());
|
||||
assertEquals("GS mismatch", 0, msg.getGeographicalScope());
|
||||
assertEquals("message code mismatch", 0, msg.getMessageCode());
|
||||
assertEquals("update number mismatch", 0, msg.getUpdateNumber());
|
||||
assertEquals("message ID mismatch", 0x1103, msg.getMessageIdentifier());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user