am 22165bb5: am ae62b7b0: resolved conflicts for merge of 66040bbb to gingerbread-plus-aosp

* commit '22165bb54af1cd3cb636c1d65da7c643d854c385':
  Fixes for SMS Cell Broadcast support.
This commit is contained in:
Jake Hamby
2011-06-09 11:29:13 -07:00
committed by Android Git Automerger
6 changed files with 654 additions and 72 deletions

View File

@@ -22,18 +22,6 @@ package android.telephony;
* {@hide}
*/
public interface SmsCbConstants {
/** Cell wide immediate geographical scope */
public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
/** PLMN wide geographical scope */
public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
/** Location / service area wide geographical scope */
public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
/** Cell wide geographical scope */
public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
/** Start of PWS Message Identifier range (includes ETWS and CMAS). */
public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER = 0x1100;
@@ -111,4 +99,19 @@ public interface SmsCbConstants {
/** ETWS message code flag to activate the emergency user alert. */
public static final int MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT = 0x200;
/** ETWS warning type value for earthquake. */
public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
/** ETWS warning type value for tsunami. */
public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01;
/** ETWS warning type value for earthquake and tsunami. */
public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02;
/** ETWS warning type value for test broadcast. */
public static final int ETWS_WARNING_TYPE_TEST = 0x03;
/** ETWS warning type value for other notifications. */
public static final int ETWS_WARNING_TYPE_OTHER = 0x04;
}

View File

@@ -16,9 +16,11 @@
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;
@@ -93,9 +95,26 @@ public class SmsCbMessage {
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);
parseBody(pdu);
if (mHeader.format == SmsCbHeader.FORMAT_ETWS_PRIMARY) {
mBody = "ETWS";
// ETWS primary notification with security is 56 octets in length
if (pdu.length >= SmsCbHeader.PDU_LENGTH_ETWS) {
mPrimaryNotificationTimestamp = getTimestampMillis(pdu);
mPrimaryNotificationDigitalSignature = new byte[43];
// digital signature starts after 6 byte header and 7 byte timestamp
System.arraycopy(pdu, 13, mPrimaryNotificationDigitalSignature, 0, 43);
}
} else {
parseBody(pdu);
}
}
/**
@@ -156,6 +175,55 @@ public class SmsCbMessage {
return mHeader.updateNumber;
}
/**
* Get the format of this message.
* @return {@link SmsCbHeader#FORMAT_GSM}, {@link SmsCbHeader#FORMAT_UMTS}, or
* {@link SmsCbHeader#FORMAT_ETWS_PRIMARY}
*/
public int getMessageFormat() {
return mHeader.format;
}
/**
* For ETWS primary notifications, return the emergency user alert flag.
* @return true to notify terminal to activate emergency user alert; false otherwise
*/
public boolean getEtwsEmergencyUserAlert() {
return mHeader.etwsEmergencyUserAlert;
}
/**
* 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
@@ -334,6 +402,55 @@ public class SmsCbMessage {
return body;
}
/**
* Parses an ETWS primary notification timestamp and returns a currentTimeMillis()-style
* timestamp. Copied from com.android.internal.telephony.gsm.SmsMessage.
* @param pdu the ETWS primary notification PDU to decode
* @return the UTC timestamp from the Warning-Security-Information parameter
*/
private long getTimestampMillis(byte[] pdu) {
// Timestamp starts after CB header, in pdu[6]
int year = IccUtils.gsmBcdByteToInt(pdu[6]);
int month = IccUtils.gsmBcdByteToInt(pdu[7]);
int day = IccUtils.gsmBcdByteToInt(pdu[8]);
int hour = IccUtils.gsmBcdByteToInt(pdu[9]);
int minute = IccUtils.gsmBcdByteToInt(pdu[10]);
int second = IccUtils.gsmBcdByteToInt(pdu[11]);
// For the timezone, the most significant bit of the
// least significant nibble is the sign byte
// (meaning the max range of this field is 79 quarter-hours,
// which is more than enough)
byte tzByte = pdu[12];
// Mask out sign bit.
int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
Time time = new Time(Time.TIMEZONE_UTC);
// It's 2006. Should I really support years < 2000?
time.year = year >= 90 ? year + 1900 : year + 2000;
time.month = month - 1;
time.monthDay = day;
time.hour = hour;
time.minute = minute;
time.second = second;
// Timezone offset is in quarter hours.
return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
}
/**
* Append text to the message body. This is used to concatenate multi-page GSM broadcasts.
* @param body the text to append to this message
*/
public void appendToBody(String body) {
mBody = mBody + body;
}
@Override
public String toString() {
return "SmsCbMessage{" + mHeader.toString() + ", language=" + mLanguage +

View File

@@ -290,54 +290,53 @@ public abstract class IntRangeManager {
return true;
} else {
// find last range that can coalesce into the new combined range
for (int endIndex = startIndex+1; endIndex < len; endIndex++) {
IntRange endRange = mRanges.get(endIndex);
if ((endId + 1) < endRange.startId) {
// add range from range.endId+1 to endId,
// values from startId to range.endId are already enabled
if (tryAddSingleRange(range.endId + 1, endId, true)) {
range.endId = endId;
// insert new ClientRange in place
range.insert(new ClientRange(startId, endId, client));
// coalesce range with following ranges up to endIndex-1
// remove each range after adding its elements, so the index
// of the next range to join is always startIndex+1.
// i is the index if no elements were removed: we only care
// about the number of loop iterations, not the value of i.
int joinIndex = startIndex + 1;
for (int i = joinIndex; i < endIndex; i++) {
IntRange joinRange = mRanges.get(joinIndex);
range.clients.addAll(joinRange.clients);
mRanges.remove(joinRange);
}
return true;
} else {
return false; // failed to update radio
}
} else if (endId <= endRange.endId) {
// add range from range.endId+1 to start of last overlapping range,
// values from endRange.startId to endId are already enabled
if (tryAddSingleRange(range.endId + 1, endRange.startId - 1, true)) {
range.endId = endRange.endId;
// insert new ClientRange in place
range.insert(new ClientRange(startId, endId, client));
// coalesce range with following ranges up to endIndex
// remove each range after adding its elements, so the index
// of the next range to join is always startIndex+1.
// i is the index if no elements were removed: we only care
// about the number of loop iterations, not the value of i.
int joinIndex = startIndex + 1;
for (int i = joinIndex; i <= endIndex; i++) {
IntRange joinRange = mRanges.get(joinIndex);
range.clients.addAll(joinRange.clients);
mRanges.remove(joinRange);
}
return true;
} else {
return false; // failed to update radio
}
int endIndex = startIndex;
for (int testIndex = startIndex+1; testIndex < len; testIndex++) {
IntRange testRange = mRanges.get(testIndex);
if ((endId + 1) < testRange.startId) {
break;
} else {
endIndex = testIndex;
}
}
// no adjacent IntRanges to combine
if (endIndex == startIndex) {
// add range from range.endId+1 to endId,
// values from startId to range.endId are already enabled
if (tryAddSingleRange(range.endId + 1, endId, true)) {
range.endId = endId;
range.insert(new ClientRange(startId, endId, client));
return true;
} else {
return false; // failed to update radio
}
}
// get last range to coalesce into start range
IntRange endRange = mRanges.get(endIndex);
// Values from startId to range.endId have already been enabled.
// if endId > endRange.endId, then enable range from range.endId+1 to endId,
// else enable range from range.endId+1 to endRange.startId-1, because
// values from endRange.startId to endId have already been added.
int newRangeEndId = (endId <= endRange.endId) ? endRange.startId - 1 : endId;
if (tryAddSingleRange(range.endId + 1, newRangeEndId, true)) {
range.endId = endId;
// insert new ClientRange in place
range.insert(new ClientRange(startId, endId, client));
// coalesce range with following ranges up to endIndex-1
// remove each range after adding its elements, so the index
// of the next range to join is always startIndex+1 (joinIndex).
// i is the index if no elements had been removed: we only care
// about the number of loop iterations, not the value of i.
int joinIndex = startIndex + 1;
for (int i = joinIndex; i < endIndex; i++) {
IntRange joinRange = mRanges.get(joinIndex);
range.clients.addAll(joinRange.clients);
mRanges.remove(joinRange);
}
return true;
} else {
return false; // failed to update radio
}
}
}
}
@@ -435,6 +434,8 @@ public abstract class IntRangeManager {
addRange(range.startId, nextStartId - 1, false);
rangeCopy.startId = nextStartId;
}
// init largestEndId
largestEndId = clients.get(1).endId;
}
// go through remaining ClientRanges, creating new IntRanges when
@@ -442,7 +443,6 @@ public abstract class IntRangeManager {
// remove the original IntRange and append newRanges to mRanges.
// Otherwise, leave the original IntRange in mRanges and return false.
ArrayList<IntRange> newRanges = new ArrayList<IntRange>();
newRanges.add(rangeCopy);
IntRange currentRange = rangeCopy;
for (int nextIndex = crIndex + 1; nextIndex < crLength; nextIndex++) {
@@ -454,6 +454,7 @@ public abstract class IntRangeManager {
}
addRange(largestEndId + 1, nextCr.startId - 1, false);
currentRange.endId = largestEndId;
newRanges.add(currentRange);
currentRange = new IntRange(nextCr);
} else {
currentRange.clients.add(nextCr);
@@ -463,18 +464,25 @@ public abstract class IntRangeManager {
}
}
if (updateStarted) {
if (!finishUpdate()) {
return false; // failed to update radio
} else {
// remove the original IntRange and insert newRanges in place.
mRanges.remove(crIndex);
mRanges.addAll(crIndex, newRanges);
return true;
// remove any channels between largestEndId and endId
if (largestEndId < endId) {
if (!updateStarted) {
startUpdate();
updateStarted = true;
}
} else {
return true;
addRange(largestEndId + 1, endId, false);
currentRange.endId = largestEndId;
}
newRanges.add(currentRange);
if (updateStarted && !finishUpdate()) {
return false; // failed to update radio
}
// replace the original IntRange with newRanges
mRanges.remove(i);
mRanges.addAll(i, newRanges);
return true;
} else {
// not the ClientRange to remove; save highest end ID seen so far
if (cr.endId > largestEndId) {

View File

@@ -34,6 +34,11 @@ public class SmsCbHeader implements SmsCbConstants {
*/
public 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;
/**
* Message type value as defined in 3gpp TS 25.324, section 11.1.
*/
@@ -42,7 +47,12 @@ public class SmsCbHeader implements SmsCbConstants {
/**
* Length of GSM pdus
*/
private static final int PDU_LENGTH_GSM = 88;
public static final int PDU_LENGTH_GSM = 88;
/**
* Maximum length of ETWS primary message GSM pdus
*/
public static final int PDU_LENGTH_ETWS = 56;
public final int geographicalScope;
@@ -60,12 +70,30 @@ public class SmsCbHeader implements SmsCbConstants {
public final int format;
public final boolean etwsEmergencyUserAlert;
public final boolean etwsPopup;
public final int etwsWarningType;
public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
throw new IllegalArgumentException("Illegal PDU");
}
if (pdu.length <= PDU_LENGTH_GSM) {
if (pdu.length <= PDU_LENGTH_ETWS) {
format = FORMAT_ETWS_PRIMARY;
geographicalScope = -1; //not applicable
messageCode = -1;
updateNumber = -1;
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;
} else if (pdu.length <= PDU_LENGTH_GSM) {
// GSM pdus are no more than 88 bytes
format = FORMAT_GSM;
geographicalScope = (pdu[0] & 0xc0) >> 6;
@@ -85,6 +113,9 @@ 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
@@ -107,6 +138,9 @@ public class SmsCbHeader implements SmsCbConstants {
// actual payload may contain several pages.
pageIndex = 1;
nrOfPages = 1;
etwsEmergencyUserAlert = false;
etwsPopup = false;
etwsWarningType = -1;
}
}