Merge changes from topic 'sms-files-move-opt-to-base'

am: abcc43d86a

Change-Id: Ib61b38a3fd065c3906e2e3f0863b83f2f4b740f4
This commit is contained in:
nharold
2017-05-06 01:55:40 +00:00
committed by android-build-merger
30 changed files with 15790 additions and 6 deletions

View File

@@ -18,7 +18,7 @@ LOCAL_SRC_FILES += \
LOCAL_AIDL_INCLUDES += \ LOCAL_AIDL_INCLUDES += \
system/netd/server/binder system/netd/server/binder
LOCAL_JAVA_LIBRARIES := services.net telephony-common LOCAL_JAVA_LIBRARIES := services.net
LOCAL_STATIC_JAVA_LIBRARIES := tzdata_shared2 tzdata_update2 LOCAL_STATIC_JAVA_LIBRARIES := tzdata_shared2 tzdata_update2
LOCAL_PROTOC_OPTIMIZE_TYPE := nano LOCAL_PROTOC_OPTIMIZE_TYPE := nano

View File

@@ -57,9 +57,8 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.telephony.IOnSubscriptionsChangedListener; import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.DefaultPhoneNotifier; import com.android.internal.telephony.PhoneConstantConversions;
import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyIntents;
import com.android.server.am.BatteryStatsService; import com.android.server.am.BatteryStatsService;
@@ -171,7 +170,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private int[] mDataConnectionNetworkType; private int[] mDataConnectionNetworkType;
private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN; private int mOtaspMode = TelephonyManager.OTASP_UNKNOWN;
private ArrayList<List<CellInfo>> mCellInfo = null; private ArrayList<List<CellInfo>> mCellInfo = null;
@@ -1496,7 +1495,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
intent.putExtra(PhoneConstants.STATE_KEY, intent.putExtra(PhoneConstants.STATE_KEY,
DefaultPhoneNotifier.convertCallState(state).toString()); PhoneConstantConversions.convertCallState(state).toString());
if (!TextUtils.isEmpty(incomingNumber)) { if (!TextUtils.isEmpty(incomingNumber)) {
intent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber); intent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber);
} }
@@ -1530,7 +1529,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
// required info. // required info.
Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
intent.putExtra(PhoneConstants.STATE_KEY, intent.putExtra(PhoneConstants.STATE_KEY,
DefaultPhoneNotifier.convertDataState(state).toString()); PhoneConstantConversions.convertDataState(state).toString());
if (!isDataConnectivityPossible) { if (!isDataConnectivityPossible) {
intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true); intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,884 @@
/*
* Copyright (C) 2008 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.Binder;
import android.os.Parcel;
import android.content.res.Resources;
import android.text.TextUtils;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
import com.android.internal.telephony.Sms7BitEncodingTranslator;
import java.lang.Math;
import java.util.ArrayList;
import java.util.Arrays;
import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
/**
* A Short Message Service message.
* @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent
*/
public class SmsMessage {
private static final String LOG_TAG = "SmsMessage";
/**
* SMS Class enumeration.
* See TS 23.038.
*
*/
public enum MessageClass{
UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
}
/** User data text encoding code unit size */
public static final int ENCODING_UNKNOWN = 0;
public static final int ENCODING_7BIT = 1;
public static final int ENCODING_8BIT = 2;
public static final int ENCODING_16BIT = 3;
/**
* @hide This value is not defined in global standard. Only in Korea, this is used.
*/
public static final int ENCODING_KSC5601 = 4;
/** The maximum number of payload bytes per message */
public static final int MAX_USER_DATA_BYTES = 140;
/**
* The maximum number of payload bytes per message if a user data header
* is present. This assumes the header only contains the
* CONCATENATED_8_BIT_REFERENCE element.
*/
public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134;
/** The maximum number of payload septets per message */
public static final int MAX_USER_DATA_SEPTETS = 160;
/**
* The maximum number of payload septets per message if a user data header
* is present. This assumes the header only contains the
* CONCATENATED_8_BIT_REFERENCE element.
*/
public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
/**
* Indicates a 3GPP format SMS message.
* @hide pending API council approval
*/
public static final String FORMAT_3GPP = "3gpp";
/**
* Indicates a 3GPP2 format SMS message.
* @hide pending API council approval
*/
public static final String FORMAT_3GPP2 = "3gpp2";
/** Contains actual SmsMessage. Only public for debugging and for framework layer.
*
* @hide
*/
public SmsMessageBase mWrappedSmsMessage;
/** Indicates the subId
*
* @hide
*/
private int mSubId = 0;
/** set Subscription information
*
* @hide
*/
public void setSubId(int subId) {
mSubId = subId;
}
/** get Subscription information
*
* @hide
*/
public int getSubId() {
return mSubId;
}
public static class SubmitPdu {
public byte[] encodedScAddress; // Null if not applicable.
public byte[] encodedMessage;
@Override
public String toString() {
return "SubmitPdu: encodedScAddress = "
+ Arrays.toString(encodedScAddress)
+ ", encodedMessage = "
+ Arrays.toString(encodedMessage);
}
/**
* @hide
*/
protected SubmitPdu(SubmitPduBase spb) {
this.encodedMessage = spb.encodedMessage;
this.encodedScAddress = spb.encodedScAddress;
}
}
/**
* @hide
*/
public SmsMessage(SmsMessageBase smb) {
mWrappedSmsMessage = smb;
}
/**
* Create an SmsMessage from a raw PDU. Guess format based on Voice
* technology first, if it fails use other format.
* All applications which handle
* incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast
* intent <b>must</b> now pass the new {@code format} String extra from the intent
* into the new method {@code createFromPdu(byte[], String)} which takes an
* extra format parameter. This is required in order to correctly decode the PDU on
* devices that require support for both 3GPP and 3GPP2 formats at the same time,
* such as dual-mode GSM/CDMA and CDMA/LTE phones.
* @deprecated Use {@link #createFromPdu(byte[], String)} instead.
*/
@Deprecated
public static SmsMessage createFromPdu(byte[] pdu) {
SmsMessage message = null;
// cdma(3gpp2) vs gsm(3gpp) format info was not given,
// guess from active voice phone type
int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
String format = (PHONE_TYPE_CDMA == activePhone) ?
SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
message = createFromPdu(pdu, format);
if (null == message || null == message.mWrappedSmsMessage) {
// decoding pdu failed based on activePhone type, must be other format
format = (PHONE_TYPE_CDMA == activePhone) ?
SmsConstants.FORMAT_3GPP : SmsConstants.FORMAT_3GPP2;
message = createFromPdu(pdu, format);
}
return message;
}
/**
* Create an SmsMessage from a raw PDU with the specified message format. The
* message format is passed in the
* {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} as the {@code format}
* String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
* or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
*
* @param pdu the message PDU from the
* {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
* @param format the format extra from the
* {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
*/
public static SmsMessage createFromPdu(byte[] pdu, String format) {
SmsMessageBase wrappedMessage;
if (SmsConstants.FORMAT_3GPP2.equals(format)) {
wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
} else if (SmsConstants.FORMAT_3GPP.equals(format)) {
wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
} else {
Rlog.e(LOG_TAG, "createFromPdu(): unsupported message format " + format);
return null;
}
if (wrappedMessage != null) {
return new SmsMessage(wrappedMessage);
} else {
Rlog.e(LOG_TAG, "createFromPdu(): wrappedMessage is null");
return null;
}
}
/**
* TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
* +CMT unsolicited response (PDU mode, of course)
* +CMT: [&lt;alpha>],<length><CR><LF><pdu>
*
* Only public for debugging and for RIL
*
* {@hide}
*/
public static SmsMessage newFromCMT(byte[] pdu) {
// received SMS in 3GPP format
SmsMessageBase wrappedMessage =
com.android.internal.telephony.gsm.SmsMessage.newFromCMT(pdu);
if (wrappedMessage != null) {
return new SmsMessage(wrappedMessage);
} else {
Rlog.e(LOG_TAG, "newFromCMT(): wrappedMessage is null");
return null;
}
}
/**
* Create an SmsMessage from an SMS EF record.
*
* @param index Index of SMS record. This should be index in ArrayList
* returned by SmsManager.getAllMessagesFromSim + 1.
* @param data Record data.
* @return An SmsMessage representing the record.
*
* @hide
*/
public static SmsMessage createFromEfRecord(int index, byte[] data) {
SmsMessageBase wrappedMessage;
if (isCdmaVoice()) {
wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
index, data);
} else {
wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
index, data);
}
if (wrappedMessage != null) {
return new SmsMessage(wrappedMessage);
} else {
Rlog.e(LOG_TAG, "createFromEfRecord(): wrappedMessage is null");
return null;
}
}
/**
* Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
* length in bytes (not hex chars) less the SMSC header
*
* FIXME: This method is only used by a CTS test case that isn't run on CDMA devices.
* We should probably deprecate it and remove the obsolete test case.
*/
public static int getTPLayerLengthForPDU(String pdu) {
if (isCdmaVoice()) {
return com.android.internal.telephony.cdma.SmsMessage.getTPLayerLengthForPDU(pdu);
} else {
return com.android.internal.telephony.gsm.SmsMessage.getTPLayerLengthForPDU(pdu);
}
}
/*
* TODO(cleanup): It would make some sense if the result of
* preprocessing a message to determine the proper encoding (i.e.
* the resulting data structure from calculateLength) could be
* passed as an argument to the actual final encoding function.
* This would better ensure that the logic behind size calculation
* actually matched the encoding.
*/
/**
* Calculates the number of SMS's required to encode the message body and
* the number of characters remaining until the next message.
*
* @param msgBody the message to encode
* @param use7bitOnly if true, characters that are not part of the
* radio-specific 7-bit encoding are counted as single
* space chars. If false, and if the messageBody contains
* non-7-bit encodable characters, length is calculated
* using a 16-bit encoding.
* @return an int[4] with int[0] being the number of SMS's
* required, int[1] the number of code units used, and
* int[2] is the number of code units remaining until the
* next message. int[3] is an indicator of the encoding
* code unit size (see the ENCODING_* definitions in SmsConstants)
*/
public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly) {
// this function is for MO SMS
TextEncodingDetails ted = (useCdmaFormatForMoSms()) ?
com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly,
true) :
com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly);
int ret[] = new int[4];
ret[0] = ted.msgCount;
ret[1] = ted.codeUnitCount;
ret[2] = ted.codeUnitsRemaining;
ret[3] = ted.codeUnitSize;
return ret;
}
/**
* Divide a message text into several fragments, none bigger than
* the maximum SMS message text size.
*
* @param text text, must not be null.
* @return an <code>ArrayList</code> of strings that, in order,
* comprise the original msg text
*
* @hide
*/
public static ArrayList<String> fragmentText(String text) {
// This function is for MO SMS
TextEncodingDetails ted = (useCdmaFormatForMoSms()) ?
com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false, true) :
com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false);
// TODO(cleanup): The code here could be rolled into the logic
// below cleanly if these MAX_* constants were defined more
// flexibly...
int limit;
if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
int udhLength;
if (ted.languageTable != 0 && ted.languageShiftTable != 0) {
udhLength = GsmAlphabet.UDH_SEPTET_COST_TWO_SHIFT_TABLES;
} else if (ted.languageTable != 0 || ted.languageShiftTable != 0) {
udhLength = GsmAlphabet.UDH_SEPTET_COST_ONE_SHIFT_TABLE;
} else {
udhLength = 0;
}
if (ted.msgCount > 1) {
udhLength += GsmAlphabet.UDH_SEPTET_COST_CONCATENATED_MESSAGE;
}
if (udhLength != 0) {
udhLength += GsmAlphabet.UDH_SEPTET_COST_LENGTH;
}
limit = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength;
} else {
if (ted.msgCount > 1) {
limit = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
// If EMS is not supported, break down EMS into single segment SMS
// and add page info " x/y".
// In the case of UCS2 encoding, we need 8 bytes for this,
// but we only have 6 bytes from UDH, so truncate the limit for
// each segment by 2 bytes (1 char).
// Make sure total number of segments is less than 10.
if (!hasEmsSupport() && ted.msgCount < 10) {
limit -= 2;
}
} else {
limit = SmsConstants.MAX_USER_DATA_BYTES;
}
}
String newMsgBody = null;
Resources r = Resources.getSystem();
if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
newMsgBody = Sms7BitEncodingTranslator.translate(text);
}
if (TextUtils.isEmpty(newMsgBody)) {
newMsgBody = text;
}
int pos = 0; // Index in code units.
int textLen = newMsgBody.length();
ArrayList<String> result = new ArrayList<String>(ted.msgCount);
while (pos < textLen) {
int nextPos = 0; // Counts code units.
if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
if (useCdmaFormatForMoSms() && ted.msgCount == 1) {
// For a singleton CDMA message, the encoding must be ASCII...
nextPos = pos + Math.min(limit, textLen - pos);
} else {
// For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode).
nextPos = GsmAlphabet.findGsmSeptetLimitIndex(newMsgBody, pos, limit,
ted.languageTable, ted.languageShiftTable);
}
} else { // Assume unicode.
nextPos = SmsMessageBase.findNextUnicodePosition(pos, limit, newMsgBody);
}
if ((nextPos <= pos) || (nextPos > textLen)) {
Rlog.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " +
nextPos + " >= " + textLen + ")");
break;
}
result.add(newMsgBody.substring(pos, nextPos));
pos = nextPos;
}
return result;
}
/**
* Calculates the number of SMS's required to encode the message body and
* the number of characters remaining until the next message, given the
* current encoding.
*
* @param messageBody the message to encode
* @param use7bitOnly if true, characters that are not part of the radio
* specific (GSM / CDMA) alphabet encoding are converted to as a
* single space characters. If false, a messageBody containing
* non-GSM or non-CDMA alphabet characters are encoded using
* 16-bit encoding.
* @return an int[4] with int[0] being the number of SMS's required, int[1]
* the number of code units used, and int[2] is the number of code
* units remaining until the next message. int[3] is the encoding
* type that should be used for the message.
*/
public static int[] calculateLength(String messageBody, boolean use7bitOnly) {
return calculateLength((CharSequence)messageBody, use7bitOnly);
}
/*
* TODO(cleanup): It looks like there is now no useful reason why
* apps should generate pdus themselves using these routines,
* instead of handing the raw data to SMSDispatcher (and thereby
* have the phone process do the encoding). Moreover, CDMA now
* has shared state (in the form of the msgId system property)
* which can only be modified by the phone process, and hence
* makes the output of these routines incorrect. Since they now
* serve no purpose, they should probably just return null
* directly, and be deprecated. Going further in that direction,
* the above parsers of serialized pdu data should probably also
* be gotten rid of, hiding all but the necessarily visible
* structured data from client apps. A possible concern with
* doing this is that apps may be using these routines to generate
* pdus that are then sent elsewhere, some network server, for
* example, and that always returning null would thereby break
* otherwise useful apps.
*/
/**
* Get an SMS-SUBMIT PDU for a destination address and a message.
* This method will not attempt to use any GSM national language 7 bit encodings.
*
* @param scAddress Service Centre address. Null means use default.
* @return a <code>SubmitPdu</code> containing the encoded SC
* address, if applicable, and the encoded message.
* Returns null on encode error.
*/
public static SubmitPdu getSubmitPdu(String scAddress,
String destinationAddress, String message, boolean statusReportRequested) {
SubmitPduBase spb;
if (useCdmaFormatForMoSms()) {
spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested, null);
} else {
spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested);
}
return new SubmitPdu(spb);
}
/**
* Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
* This method will not attempt to use any GSM national language 7 bit encodings.
*
* @param scAddress Service Centre address. null == use default
* @param destinationAddress the address of the destination for the message
* @param destinationPort the port to deliver the message to at the
* destination
* @param data the data for the message
* @return a <code>SubmitPdu</code> containing the encoded SC
* address, if applicable, and the encoded message.
* Returns null on encode error.
*/
public static SubmitPdu getSubmitPdu(String scAddress,
String destinationAddress, short destinationPort, byte[] data,
boolean statusReportRequested) {
SubmitPduBase spb;
if (useCdmaFormatForMoSms()) {
spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, destinationPort, data, statusReportRequested);
} else {
spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, destinationPort, data, statusReportRequested);
}
return new SubmitPdu(spb);
}
/**
* Returns the address of the SMS service center that relayed this message
* or null if there is none.
*/
public String getServiceCenterAddress() {
return mWrappedSmsMessage.getServiceCenterAddress();
}
/**
* Returns the originating address (sender) of this SMS message in String
* form or null if unavailable
*/
public String getOriginatingAddress() {
return mWrappedSmsMessage.getOriginatingAddress();
}
/**
* Returns the originating address, or email from address if this message
* was from an email gateway. Returns null if originating address
* unavailable.
*/
public String getDisplayOriginatingAddress() {
return mWrappedSmsMessage.getDisplayOriginatingAddress();
}
/**
* Returns the message body as a String, if it exists and is text based.
* @return message body is there is one, otherwise null
*/
public String getMessageBody() {
return mWrappedSmsMessage.getMessageBody();
}
/**
* Returns the class of this message.
*/
public MessageClass getMessageClass() {
switch(mWrappedSmsMessage.getMessageClass()) {
case CLASS_0: return MessageClass.CLASS_0;
case CLASS_1: return MessageClass.CLASS_1;
case CLASS_2: return MessageClass.CLASS_2;
case CLASS_3: return MessageClass.CLASS_3;
default: return MessageClass.UNKNOWN;
}
}
/**
* Returns the message body, or email message body if this message was from
* an email gateway. Returns null if message body unavailable.
*/
public String getDisplayMessageBody() {
return mWrappedSmsMessage.getDisplayMessageBody();
}
/**
* Unofficial convention of a subject line enclosed in parens empty string
* if not present
*/
public String getPseudoSubject() {
return mWrappedSmsMessage.getPseudoSubject();
}
/**
* Returns the service centre timestamp in currentTimeMillis() format
*/
public long getTimestampMillis() {
return mWrappedSmsMessage.getTimestampMillis();
}
/**
* Returns true if message is an email.
*
* @return true if this message came through an email gateway and email
* sender / subject / parsed body are available
*/
public boolean isEmail() {
return mWrappedSmsMessage.isEmail();
}
/**
* @return if isEmail() is true, body of the email sent through the gateway.
* null otherwise
*/
public String getEmailBody() {
return mWrappedSmsMessage.getEmailBody();
}
/**
* @return if isEmail() is true, email from address of email sent through
* the gateway. null otherwise
*/
public String getEmailFrom() {
return mWrappedSmsMessage.getEmailFrom();
}
/**
* Get protocol identifier.
*/
public int getProtocolIdentifier() {
return mWrappedSmsMessage.getProtocolIdentifier();
}
/**
* See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
* SMS
*/
public boolean isReplace() {
return mWrappedSmsMessage.isReplace();
}
/**
* Returns true for CPHS MWI toggle message.
*
* @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
* B.4.2
*/
public boolean isCphsMwiMessage() {
return mWrappedSmsMessage.isCphsMwiMessage();
}
/**
* returns true if this message is a CPHS voicemail / message waiting
* indicator (MWI) clear message
*/
public boolean isMWIClearMessage() {
return mWrappedSmsMessage.isMWIClearMessage();
}
/**
* returns true if this message is a CPHS voicemail / message waiting
* indicator (MWI) set message
*/
public boolean isMWISetMessage() {
return mWrappedSmsMessage.isMWISetMessage();
}
/**
* returns true if this message is a "Message Waiting Indication Group:
* Discard Message" notification and should not be stored.
*/
public boolean isMwiDontStore() {
return mWrappedSmsMessage.isMwiDontStore();
}
/**
* returns the user data section minus the user data header if one was
* present.
*/
public byte[] getUserData() {
return mWrappedSmsMessage.getUserData();
}
/**
* Returns the raw PDU for the message.
*
* @return the raw PDU for the message.
*/
public byte[] getPdu() {
return mWrappedSmsMessage.getPdu();
}
/**
* Returns the status of the message on the SIM (read, unread, sent, unsent).
*
* @return the status of the message on the SIM. These are:
* SmsManager.STATUS_ON_SIM_FREE
* SmsManager.STATUS_ON_SIM_READ
* SmsManager.STATUS_ON_SIM_UNREAD
* SmsManager.STATUS_ON_SIM_SEND
* SmsManager.STATUS_ON_SIM_UNSENT
* @deprecated Use getStatusOnIcc instead.
*/
@Deprecated public int getStatusOnSim() {
return mWrappedSmsMessage.getStatusOnIcc();
}
/**
* Returns the status of the message on the ICC (read, unread, sent, unsent).
*
* @return the status of the message on the ICC. These are:
* SmsManager.STATUS_ON_ICC_FREE
* SmsManager.STATUS_ON_ICC_READ
* SmsManager.STATUS_ON_ICC_UNREAD
* SmsManager.STATUS_ON_ICC_SEND
* SmsManager.STATUS_ON_ICC_UNSENT
*/
public int getStatusOnIcc() {
return mWrappedSmsMessage.getStatusOnIcc();
}
/**
* Returns the record index of the message on the SIM (1-based index).
* @return the record index of the message on the SIM, or -1 if this
* SmsMessage was not created from a SIM SMS EF record.
* @deprecated Use getIndexOnIcc instead.
*/
@Deprecated public int getIndexOnSim() {
return mWrappedSmsMessage.getIndexOnIcc();
}
/**
* Returns the record index of the message on the ICC (1-based index).
* @return the record index of the message on the ICC, or -1 if this
* SmsMessage was not created from a ICC SMS EF record.
*/
public int getIndexOnIcc() {
return mWrappedSmsMessage.getIndexOnIcc();
}
/**
* GSM:
* For an SMS-STATUS-REPORT message, this returns the status field from
* the status report. This field indicates the status of a previously
* submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a
* description of values.
* CDMA:
* For not interfering with status codes from GSM, the value is
* shifted to the bits 31-16.
* The value is composed of an error class (bits 25-24) and a status code (bits 23-16).
* Possible codes are described in C.S0015-B, v2.0, 4.5.21.
*
* @return 0 indicates the previously sent message was received.
* See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21
* for a description of other possible values.
*/
public int getStatus() {
return mWrappedSmsMessage.getStatus();
}
/**
* Return true iff the message is a SMS-STATUS-REPORT message.
*/
public boolean isStatusReportMessage() {
return mWrappedSmsMessage.isStatusReportMessage();
}
/**
* Returns true iff the <code>TP-Reply-Path</code> bit is set in
* this message.
*/
public boolean isReplyPathPresent() {
return mWrappedSmsMessage.isReplyPathPresent();
}
/**
* Determines whether or not to use CDMA format for MO SMS.
* If SMS over IMS is supported, then format is based on IMS SMS format,
* otherwise format is based on current phone type.
*
* @return true if Cdma format should be used for MO SMS, false otherwise.
*/
private static boolean useCdmaFormatForMoSms() {
if (!SmsManager.getDefault().isImsSmsSupported()) {
// use Voice technology to determine SMS format.
return isCdmaVoice();
}
// IMS is registered with SMS support, check the SMS format supported
return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
}
/**
* Determines whether or not to current phone type is cdma.
*
* @return true if current phone type is cdma, false otherwise.
*/
private static boolean isCdmaVoice() {
int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
return (PHONE_TYPE_CDMA == activePhone);
}
/**
* Decide if the carrier supports long SMS.
* {@hide}
*/
public static boolean hasEmsSupport() {
if (!isNoEmsSupportConfigListExisted()) {
return true;
}
String simOperator;
String gid;
final long identity = Binder.clearCallingIdentity();
try {
simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
gid = TelephonyManager.getDefault().getGroupIdLevel1();
} finally {
Binder.restoreCallingIdentity(identity);
}
if (!TextUtils.isEmpty(simOperator)) {
for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
(TextUtils.isEmpty(currentConfig.mGid1) ||
(!TextUtils.isEmpty(currentConfig.mGid1) &&
currentConfig.mGid1.equalsIgnoreCase(gid)))) {
return false;
}
}
}
return true;
}
/**
* Check where to add " x/y" in each SMS segment, begin or end.
* {@hide}
*/
public static boolean shouldAppendPageNumberAsPrefix() {
if (!isNoEmsSupportConfigListExisted()) {
return false;
}
String simOperator;
String gid;
final long identity = Binder.clearCallingIdentity();
try {
simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
gid = TelephonyManager.getDefault().getGroupIdLevel1();
} finally {
Binder.restoreCallingIdentity(identity);
}
for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
(TextUtils.isEmpty(currentConfig.mGid1) ||
(!TextUtils.isEmpty(currentConfig.mGid1)
&& currentConfig.mGid1.equalsIgnoreCase(gid)))) {
return currentConfig.mIsPrefix;
}
}
return false;
}
private static class NoEmsSupportConfig {
String mOperatorNumber;
String mGid1;
boolean mIsPrefix;
public NoEmsSupportConfig(String[] config) {
mOperatorNumber = config[0];
mIsPrefix = "prefix".equals(config[1]);
mGid1 = config.length > 2 ? config[2] : null;
}
@Override
public String toString() {
return "NoEmsSupportConfig { mOperatorNumber = " + mOperatorNumber
+ ", mIsPrefix = " + mIsPrefix + ", mGid1 = " + mGid1 + " }";
}
}
private static NoEmsSupportConfig[] mNoEmsSupportConfigList = null;
private static boolean mIsNoEmsSupportConfigListLoaded = false;
private static boolean isNoEmsSupportConfigListExisted() {
if (!mIsNoEmsSupportConfigListLoaded) {
Resources r = Resources.getSystem();
if (r != null) {
String[] listArray = r.getStringArray(
com.android.internal.R.array.no_ems_support_sim_operators);
if ((listArray != null) && (listArray.length > 0)) {
mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length];
for (int i=0; i<listArray.length; i++) {
mNoEmsSupportConfigList[i] = new NoEmsSupportConfig(listArray[i].split(";"));
}
}
mIsNoEmsSupportConfigListLoaded = true;
}
}
if (mNoEmsSupportConfigList != null && mNoEmsSupportConfigList.length != 0) {
return true;
}
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -127,6 +127,20 @@ public class TelephonyManager {
static final int NEVER_USE = 2; static final int NEVER_USE = 2;
} }
/** The otaspMode passed to PhoneStateListener#onOtaspChanged */
/** @hide */
static public final int OTASP_UNINITIALIZED = 0;
/** @hide */
static public final int OTASP_UNKNOWN = 1;
/** @hide */
static public final int OTASP_NEEDED = 2;
/** @hide */
static public final int OTASP_NOT_NEEDED = 3;
/* OtaUtil has conflict enum 4: OtaUtils.OTASP_FAILURE_SPC_RETRIES */
/** @hide */
static public final int OTASP_SIM_UNPROVISIONED = 5;
private final Context mContext; private final Context mContext;
private final int mSubId; private final int mSubId;
private SubscriptionManager mSubscriptionManager; private SubscriptionManager mSubscriptionManager;

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.PreciseCallState;
import com.android.internal.telephony.PhoneConstants;
import java.util.List;
public class PhoneConstantConversions {
/**
* Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
* constants for the public API.
*/
public static int convertCallState(PhoneConstants.State state) {
switch (state) {
case RINGING:
return TelephonyManager.CALL_STATE_RINGING;
case OFFHOOK:
return TelephonyManager.CALL_STATE_OFFHOOK;
default:
return TelephonyManager.CALL_STATE_IDLE;
}
}
/**
* Convert the TelephonyManager.CALL_STATE_* constants into the
* {@link PhoneConstants.State} enum for the public API.
*/
public static PhoneConstants.State convertCallState(int state) {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
return PhoneConstants.State.RINGING;
case TelephonyManager.CALL_STATE_OFFHOOK:
return PhoneConstants.State.OFFHOOK;
default:
return PhoneConstants.State.IDLE;
}
}
/**
* Convert the {@link PhoneConstants.DataState} enum into the TelephonyManager.DATA_* constants
* for the public API.
*/
public static int convertDataState(PhoneConstants.DataState state) {
switch (state) {
case CONNECTING:
return TelephonyManager.DATA_CONNECTING;
case CONNECTED:
return TelephonyManager.DATA_CONNECTED;
case SUSPENDED:
return TelephonyManager.DATA_SUSPENDED;
default:
return TelephonyManager.DATA_DISCONNECTED;
}
}
/**
* Convert the TelephonyManager.DATA_* constants into {@link PhoneConstants.DataState} enum
* for the public API.
*/
public static PhoneConstants.DataState convertDataState(int state) {
switch (state) {
case TelephonyManager.DATA_CONNECTING:
return PhoneConstants.DataState.CONNECTING;
case TelephonyManager.DATA_CONNECTED:
return PhoneConstants.DataState.CONNECTED;
case TelephonyManager.DATA_SUSPENDED:
return PhoneConstants.DataState.SUSPENDED;
default:
return PhoneConstants.DataState.DISCONNECTED;
}
}
}

View File

@@ -0,0 +1,235 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import android.telephony.Rlog;
import android.os.Build;
import android.util.SparseIntArray;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import com.android.internal.util.XmlUtils;
import com.android.internal.telephony.cdma.sms.UserData;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
public class Sms7BitEncodingTranslator {
private static final String TAG = "Sms7BitEncodingTranslator";
private static final boolean DBG = Build.IS_DEBUGGABLE ;
private static boolean mIs7BitTranslationTableLoaded = false;
private static SparseIntArray mTranslationTable = null;
private static SparseIntArray mTranslationTableCommon = null;
private static SparseIntArray mTranslationTableGSM = null;
private static SparseIntArray mTranslationTableCDMA = null;
// Parser variables
private static final String XML_START_TAG = "SmsEnforce7BitTranslationTable";
private static final String XML_TRANSLATION_TYPE_TAG = "TranslationType";
private static final String XML_CHARACTOR_TAG = "Character";
private static final String XML_FROM_TAG = "from";
private static final String XML_TO_TAG = "to";
/**
* Translates each message character that is not supported by GSM 7bit
* alphabet into a supported one
*
* @param message
* message to be translated
* @param throwsException
* if true and some error occurs during translation, an exception
* is thrown; otherwise a null String is returned
* @return translated message or null if some error occur
*/
public static String translate(CharSequence message) {
if (message == null) {
Rlog.w(TAG, "Null message can not be translated");
return null;
}
int size = message.length();
if (size <= 0) {
return "";
}
if (!mIs7BitTranslationTableLoaded) {
mTranslationTableCommon = new SparseIntArray();
mTranslationTableGSM = new SparseIntArray();
mTranslationTableCDMA = new SparseIntArray();
load7BitTranslationTableFromXml();
mIs7BitTranslationTableLoaded = true;
}
if ((mTranslationTableCommon != null && mTranslationTableCommon.size() > 0) ||
(mTranslationTableGSM != null && mTranslationTableGSM.size() > 0) ||
(mTranslationTableCDMA != null && mTranslationTableCDMA.size() > 0)) {
char[] output = new char[size];
boolean isCdmaFormat = useCdmaFormatForMoSms();
for (int i = 0; i < size; i++) {
output[i] = translateIfNeeded(message.charAt(i), isCdmaFormat);
}
return String.valueOf(output);
}
return null;
}
/**
* Translates a single character into its corresponding acceptable one, if
* needed, based on GSM 7-bit alphabet
*
* @param c
* character to be translated
* @return original character, if it's present on GSM 7-bit alphabet; a
* corresponding character, based on the translation table or white
* space, if no mapping is found in the translation table for such
* character
*/
private static char translateIfNeeded(char c, boolean isCdmaFormat) {
if (noTranslationNeeded(c, isCdmaFormat)) {
if (DBG) {
Rlog.v(TAG, "No translation needed for " + Integer.toHexString(c));
}
return c;
}
/*
* Trying to translate unicode to Gsm 7-bit alphabet; If c is not
* present on translation table, c does not belong to Unicode Latin-1
* (Basic + Supplement), so we don't know how to translate it to a Gsm
* 7-bit character! We replace c for an empty space and advises the user
* about it.
*/
int translation = -1;
if (mTranslationTableCommon != null) {
translation = mTranslationTableCommon.get(c, -1);
}
if (translation == -1) {
if (isCdmaFormat) {
if (mTranslationTableCDMA != null) {
translation = mTranslationTableCDMA.get(c, -1);
}
} else {
if (mTranslationTableGSM != null) {
translation = mTranslationTableGSM.get(c, -1);
}
}
}
if (translation != -1) {
if (DBG) {
Rlog.v(TAG, Integer.toHexString(c) + " (" + c + ")" + " translated to "
+ Integer.toHexString(translation) + " (" + (char) translation + ")");
}
return (char) translation;
} else {
if (DBG) {
Rlog.w(TAG, "No translation found for " + Integer.toHexString(c)
+ "! Replacing for empty space");
}
return ' ';
}
}
private static boolean noTranslationNeeded(char c, boolean isCdmaFormat) {
if (isCdmaFormat) {
return GsmAlphabet.isGsmSeptets(c) && UserData.charToAscii.get(c, -1) != -1;
}
else {
return GsmAlphabet.isGsmSeptets(c);
}
}
private static boolean useCdmaFormatForMoSms() {
if (!SmsManager.getDefault().isImsSmsSupported()) {
// use Voice technology to determine SMS format.
return TelephonyManager.getDefault().getCurrentPhoneType()
== PhoneConstants.PHONE_TYPE_CDMA;
}
// IMS is registered with SMS support, check the SMS format supported
return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
}
/**
* Load the whole translation table file from the framework resource
* encoded in XML.
*/
private static void load7BitTranslationTableFromXml() {
XmlResourceParser parser = null;
Resources r = Resources.getSystem();
if (parser == null) {
if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: open normal file");
parser = r.getXml(com.android.internal.R.xml.sms_7bit_translation_table);
}
try {
XmlUtils.beginDocument(parser, XML_START_TAG);
while (true) {
XmlUtils.nextElement(parser);
String tag = parser.getName();
if (DBG) {
Rlog.d(TAG, "tag: " + tag);
}
if (XML_TRANSLATION_TYPE_TAG.equals(tag)) {
String type = parser.getAttributeValue(null, "Type");
if (DBG) {
Rlog.d(TAG, "type: " + type);
}
if (type.equals("common")) {
mTranslationTable = mTranslationTableCommon;
} else if (type.equals("gsm")) {
mTranslationTable = mTranslationTableGSM;
} else if (type.equals("cdma")) {
mTranslationTable = mTranslationTableCDMA;
} else {
Rlog.e(TAG, "Error Parsing 7BitTranslationTable: found incorrect type" + type);
}
} else if (XML_CHARACTOR_TAG.equals(tag) && mTranslationTable != null) {
int from = parser.getAttributeUnsignedIntValue(null,
XML_FROM_TAG, -1);
int to = parser.getAttributeUnsignedIntValue(null,
XML_TO_TAG, -1);
if ((from != -1) && (to != -1)) {
if (DBG) {
Rlog.d(TAG, "Loading mapping " + Integer.toHexString(from)
.toUpperCase() + " -> " + Integer.toHexString(to)
.toUpperCase());
}
mTranslationTable.put (from, to);
} else {
Rlog.d(TAG, "Invalid translation table file format");
}
} else {
break;
}
}
if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: parsing successful, file loaded");
} catch (Exception e) {
Rlog.e(TAG, "Got exception while loading 7BitTranslationTable file.", e);
} finally {
if (parser instanceof XmlResourceParser) {
((XmlResourceParser)parser).close();
}
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2008 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;
public abstract class SmsAddress {
// From TS 23.040 9.1.2.5 and TS 24.008 table 10.5.118
// and C.S0005-D table 2.7.1.3.2.4-2
public static final int TON_UNKNOWN = 0;
public static final int TON_INTERNATIONAL = 1;
public static final int TON_NATIONAL = 2;
public static final int TON_NETWORK = 3;
public static final int TON_SUBSCRIBER = 4;
public static final int TON_ALPHANUMERIC = 5;
public static final int TON_ABBREVIATED = 6;
public int ton;
public String address;
public byte[] origBytes;
/**
* Returns the address of the SMS message in String form or null if unavailable
*/
public String getAddressString() {
return address;
}
/**
* Returns true if this is an alphanumeric address
*/
public boolean isAlphanumeric() {
return ton == TON_ALPHANUMERIC;
}
/**
* Returns true if this is a network address
*/
public boolean isNetworkSpecific() {
return ton == TON_NETWORK;
}
public boolean couldBeEmailGateway() {
// Some carriers seems to send email gateway messages in this form:
// from: an UNKNOWN TON, 3 or 4 digits long, beginning with a 5
// PID: 0x00, Data coding scheme 0x03
// So we just attempt to treat any message from an address length <= 4
// as an email gateway
return address.length() <= 4;
}
}

View File

@@ -0,0 +1,980 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import android.Manifest.permission;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Binder;
import android.os.Debug;
import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
import android.telephony.Rlog;
import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
/**
* Class for managing the primary application that we will deliver SMS/MMS messages to
*
* {@hide}
*/
public final class SmsApplication {
static final String LOG_TAG = "SmsApplication";
private static final String PHONE_PACKAGE_NAME = "com.android.phone";
private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
private static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service";
private static final String TELEPHONY_PROVIDER_PACKAGE_NAME = "com.android.providers.telephony";
private static final String SCHEME_SMS = "sms";
private static final String SCHEME_SMSTO = "smsto";
private static final String SCHEME_MMS = "mms";
private static final String SCHEME_MMSTO = "mmsto";
private static final boolean DEBUG_MULTIUSER = false;
private static SmsPackageMonitor sSmsPackageMonitor = null;
public static class SmsApplicationData {
/**
* Name of this SMS app for display.
*/
private String mApplicationName;
/**
* Package name for this SMS app.
*/
public String mPackageName;
/**
* The class name of the SMS_DELIVER_ACTION receiver in this app.
*/
private String mSmsReceiverClass;
/**
* The class name of the WAP_PUSH_DELIVER_ACTION receiver in this app.
*/
private String mMmsReceiverClass;
/**
* The class name of the ACTION_RESPOND_VIA_MESSAGE intent in this app.
*/
private String mRespondViaMessageClass;
/**
* The class name of the ACTION_SENDTO intent in this app.
*/
private String mSendToClass;
/**
* The class name of the ACTION_DEFAULT_SMS_PACKAGE_CHANGED receiver in this app.
*/
private String mSmsAppChangedReceiverClass;
/**
* The class name of the ACTION_EXTERNAL_PROVIDER_CHANGE receiver in this app.
*/
private String mProviderChangedReceiverClass;
/**
* The class name of the SIM_FULL_ACTION receiver in this app.
*/
private String mSimFullReceiverClass;
/**
* The user-id for this application
*/
private int mUid;
/**
* Returns true if this SmsApplicationData is complete (all intents handled).
* @return
*/
public boolean isComplete() {
return (mSmsReceiverClass != null && mMmsReceiverClass != null
&& mRespondViaMessageClass != null && mSendToClass != null);
}
public SmsApplicationData(String packageName, int uid) {
mPackageName = packageName;
mUid = uid;
}
public String getApplicationName(Context context) {
if (mApplicationName == null) {
PackageManager pm = context.getPackageManager();
ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfoAsUser(mPackageName, 0,
UserHandle.getUserId(mUid));
} catch (NameNotFoundException e) {
return null;
}
if (appInfo != null) {
CharSequence label = pm.getApplicationLabel(appInfo);
mApplicationName = (label == null) ? null : label.toString();
}
}
return mApplicationName;
}
@Override
public String toString() {
return " mPackageName: " + mPackageName
+ " mSmsReceiverClass: " + mSmsReceiverClass
+ " mMmsReceiverClass: " + mMmsReceiverClass
+ " mRespondViaMessageClass: " + mRespondViaMessageClass
+ " mSendToClass: " + mSendToClass
+ " mSmsAppChangedClass: " + mSmsAppChangedReceiverClass
+ " mProviderChangedReceiverClass: " + mProviderChangedReceiverClass
+ " mSimFullReceiverClass: " + mSimFullReceiverClass
+ " mUid: " + mUid;
}
}
/**
* Returns the userId of the Context object, if called from a system app,
* otherwise it returns the caller's userId
* @param context The context object passed in by the caller.
* @return
*/
private static int getIncomingUserId(Context context) {
int contextUserId = context.getUserId();
final int callingUid = Binder.getCallingUid();
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getIncomingUserHandle caller=" + callingUid + ", myuid="
+ android.os.Process.myUid() + "\n\t" + Debug.getCallers(4));
}
if (UserHandle.getAppId(callingUid)
< android.os.Process.FIRST_APPLICATION_UID) {
return contextUserId;
} else {
return UserHandle.getUserId(callingUid);
}
}
/**
* Returns the list of available SMS apps defined as apps that are registered for both the
* SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast
* receivers are enabled)
*
* Requirements to be an SMS application:
* Implement SMS_DELIVER_ACTION broadcast receiver.
* Require BROADCAST_SMS permission.
*
* Implement WAP_PUSH_DELIVER_ACTION broadcast receiver.
* Require BROADCAST_WAP_PUSH permission.
*
* Implement RESPOND_VIA_MESSAGE intent.
* Support smsto Uri scheme.
* Require SEND_RESPOND_VIA_MESSAGE permission.
*
* Implement ACTION_SENDTO intent.
* Support smsto Uri scheme.
*/
public static Collection<SmsApplicationData> getApplicationCollection(Context context) {
int userId = getIncomingUserId(context);
final long token = Binder.clearCallingIdentity();
try {
return getApplicationCollectionInternal(context, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private static Collection<SmsApplicationData> getApplicationCollectionInternal(
Context context, int userId) {
PackageManager packageManager = context.getPackageManager();
// Get the list of apps registered for SMS
Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
userId);
HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();
// Add one entry to the map for every sms receiver (ignoring duplicate sms receivers)
for (ResolveInfo resolveInfo : smsReceivers) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
continue;
}
if (!permission.BROADCAST_SMS.equals(activityInfo.permission)) {
continue;
}
final String packageName = activityInfo.packageName;
if (!receivers.containsKey(packageName)) {
final SmsApplicationData smsApplicationData = new SmsApplicationData(packageName,
activityInfo.applicationInfo.uid);
smsApplicationData.mSmsReceiverClass = activityInfo.name;
receivers.put(packageName, smsApplicationData);
}
}
// Update any existing entries with mms receiver class
intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
intent.setDataAndType(null, "application/vnd.wap.mms-message");
List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
userId);
for (ResolveInfo resolveInfo : mmsReceivers) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
continue;
}
if (!permission.BROADCAST_WAP_PUSH.equals(activityInfo.permission)) {
continue;
}
final String packageName = activityInfo.packageName;
final SmsApplicationData smsApplicationData = receivers.get(packageName);
if (smsApplicationData != null) {
smsApplicationData.mMmsReceiverClass = activityInfo.name;
}
}
// Update any existing entries with respond via message intent class.
intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE,
Uri.fromParts(SCHEME_SMSTO, "", null));
List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent, 0,
userId);
for (ResolveInfo resolveInfo : respondServices) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
if (serviceInfo == null) {
continue;
}
if (!permission.SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) {
continue;
}
final String packageName = serviceInfo.packageName;
final SmsApplicationData smsApplicationData = receivers.get(packageName);
if (smsApplicationData != null) {
smsApplicationData.mRespondViaMessageClass = serviceInfo.name;
}
}
// Update any existing entries with supports send to.
intent = new Intent(Intent.ACTION_SENDTO,
Uri.fromParts(SCHEME_SMSTO, "", null));
List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, 0,
userId);
for (ResolveInfo resolveInfo : sendToActivities) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
continue;
}
final String packageName = activityInfo.packageName;
final SmsApplicationData smsApplicationData = receivers.get(packageName);
if (smsApplicationData != null) {
smsApplicationData.mSendToClass = activityInfo.name;
}
}
// Update any existing entries with the default sms changed handler.
intent = new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
List<ResolveInfo> smsAppChangedReceivers =
packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" +
smsAppChangedReceivers);
}
for (ResolveInfo resolveInfo : smsAppChangedReceivers) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
continue;
}
final String packageName = activityInfo.packageName;
final SmsApplicationData smsApplicationData = receivers.get(packageName);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" +
packageName + " smsApplicationData: " + smsApplicationData +
" activityInfo.name: " + activityInfo.name);
}
if (smsApplicationData != null) {
smsApplicationData.mSmsAppChangedReceiverClass = activityInfo.name;
}
}
// Update any existing entries with the external provider changed handler.
intent = new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE);
List<ResolveInfo> providerChangedReceivers =
packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" +
providerChangedReceivers);
}
for (ResolveInfo resolveInfo : providerChangedReceivers) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
continue;
}
final String packageName = activityInfo.packageName;
final SmsApplicationData smsApplicationData = receivers.get(packageName);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" +
packageName + " smsApplicationData: " + smsApplicationData +
" activityInfo.name: " + activityInfo.name);
}
if (smsApplicationData != null) {
smsApplicationData.mProviderChangedReceiverClass = activityInfo.name;
}
}
// Update any existing entries with the sim full handler.
intent = new Intent(Intents.SIM_FULL_ACTION);
List<ResolveInfo> simFullReceivers =
packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers="
+ simFullReceivers);
}
for (ResolveInfo resolveInfo : simFullReceivers) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
continue;
}
final String packageName = activityInfo.packageName;
final SmsApplicationData smsApplicationData = receivers.get(packageName);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal packageName="
+ packageName + " smsApplicationData: " + smsApplicationData
+ " activityInfo.name: " + activityInfo.name);
}
if (smsApplicationData != null) {
smsApplicationData.mSimFullReceiverClass = activityInfo.name;
}
}
// Remove any entries for which we did not find all required intents.
for (ResolveInfo resolveInfo : smsReceivers) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
continue;
}
final String packageName = activityInfo.packageName;
final SmsApplicationData smsApplicationData = receivers.get(packageName);
if (smsApplicationData != null) {
if (!smsApplicationData.isComplete()) {
receivers.remove(packageName);
}
}
}
return receivers.values();
}
/**
* Checks to see if we have a valid installed SMS application for the specified package name
* @return Data for the specified package name or null if there isn't one
*/
private static SmsApplicationData getApplicationForPackage(
Collection<SmsApplicationData> applications, String packageName) {
if (packageName == null) {
return null;
}
// Is there an entry in the application list for the specified package?
for (SmsApplicationData application : applications) {
if (application.mPackageName.contentEquals(packageName)) {
return application;
}
}
return null;
}
/**
* Get the application we will use for delivering SMS/MMS messages.
*
* We return the preferred sms application with the following order of preference:
* (1) User selected SMS app (if selected, and if still valid)
* (2) Android Messaging (if installed)
* (3) The currently configured highest priority broadcast receiver
* (4) Null
*/
private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded,
int userId) {
TelephonyManager tm = (TelephonyManager)
context.getSystemService(Context.TELEPHONY_SERVICE);
if (!tm.isSmsCapable()) {
// No phone, no SMS
return null;
}
Collection<SmsApplicationData> applications = getApplicationCollectionInternal(context,
userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplication userId=" + userId);
}
// Determine which application receives the broadcast
String defaultApplication = Settings.Secure.getStringForUser(context.getContentResolver(),
Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplication defaultApp=" + defaultApplication);
}
SmsApplicationData applicationData = null;
if (defaultApplication != null) {
applicationData = getApplicationForPackage(applications, defaultApplication);
}
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplication appData=" + applicationData);
}
// Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do
// this if the caller asked us to.
if (updateIfNeeded && applicationData == null) {
// Try to find the default SMS package for this device
Resources r = context.getResources();
String defaultPackage =
r.getString(com.android.internal.R.string.default_sms_application);
applicationData = getApplicationForPackage(applications, defaultPackage);
if (applicationData == null) {
// Are there any applications?
if (applications.size() != 0) {
applicationData = (SmsApplicationData)applications.toArray()[0];
}
}
// If we found a new default app, update the setting
if (applicationData != null) {
setDefaultApplicationInternal(applicationData.mPackageName, context, userId);
}
}
// If we found a package, make sure AppOps permissions are set up correctly
if (applicationData != null) {
AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
// We can only call checkOp if we are privileged (updateIfNeeded) or if the app we
// are checking is for our current uid. Doing this check from the unprivileged current
// SMS app allows us to tell the current SMS app that it is not in a good state and
// needs to ask to be the current SMS app again to work properly.
if (updateIfNeeded || applicationData.mUid == android.os.Process.myUid()) {
// Verify that the SMS app has permissions
int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
applicationData.mPackageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
Rlog.e(LOG_TAG, applicationData.mPackageName + " lost OP_WRITE_SMS: " +
(updateIfNeeded ? " (fixing)" : " (no permission to fix)"));
if (updateIfNeeded) {
appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
} else {
// We can not return a package if permissions are not set up correctly
applicationData = null;
}
}
}
// We can only verify the phone and BT app's permissions from a privileged caller
if (updateIfNeeded) {
// Ensure this component is still configured as the preferred activity. Usually the
// current SMS app will already be the preferred activity - but checking whether or
// not this is true is just as expensive as reconfiguring the preferred activity so
// we just reconfigure every time.
PackageManager packageManager = context.getPackageManager();
configurePreferredActivity(packageManager, new ComponentName(
applicationData.mPackageName, applicationData.mSendToClass),
userId);
// Assign permission to special system apps
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
PHONE_PACKAGE_NAME);
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
BLUETOOTH_PACKAGE_NAME);
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
MMS_SERVICE_PACKAGE_NAME);
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
TELEPHONY_PROVIDER_PACKAGE_NAME);
// Give WRITE_SMS AppOps permission to UID 1001 which contains multiple
// apps, all of them should be able to write to telephony provider.
// This is to allow the proxy package permission check in telephony provider
// to pass.
assignWriteSmsPermissionToSystemUid(appOps, Process.PHONE_UID);
}
}
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplication returning appData=" + applicationData);
}
return applicationData;
}
/**
* Sets the specified package as the default SMS/MMS application. The caller of this method
* needs to have permission to set AppOps and write to secure settings.
*/
public static void setDefaultApplication(String packageName, Context context) {
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
if (!tm.isSmsCapable()) {
// No phone, no SMS
return;
}
final int userId = getIncomingUserId(context);
final long token = Binder.clearCallingIdentity();
try {
setDefaultApplicationInternal(packageName, context, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private static void setDefaultApplicationInternal(String packageName, Context context,
int userId) {
// Get old package name
String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(),
Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldPackageName +
" new=" + packageName);
}
if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) {
// No change
return;
}
// We only make the change if the new package is valid
PackageManager packageManager = context.getPackageManager();
Collection<SmsApplicationData> applications = getApplicationCollection(context);
SmsApplicationData oldAppData = oldPackageName != null ?
getApplicationForPackage(applications, oldPackageName) : null;
SmsApplicationData applicationData = getApplicationForPackage(applications, packageName);
if (applicationData != null) {
// Ignore OP_WRITE_SMS for the previously configured default SMS app.
AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
if (oldPackageName != null) {
try {
PackageInfo info = packageManager.getPackageInfo(oldPackageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
oldPackageName, AppOpsManager.MODE_IGNORED);
} catch (NameNotFoundException e) {
Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
}
}
// Update the secure setting.
Settings.Secure.putStringForUser(context.getContentResolver(),
Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName,
userId);
// Configure this as the preferred activity for SENDTO sms/mms intents
configurePreferredActivity(packageManager, new ComponentName(
applicationData.mPackageName, applicationData.mSendToClass), userId);
// Allow OP_WRITE_SMS for the newly configured default SMS app.
appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
// Assign permission to special system apps
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
PHONE_PACKAGE_NAME);
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
BLUETOOTH_PACKAGE_NAME);
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
MMS_SERVICE_PACKAGE_NAME);
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
TELEPHONY_PROVIDER_PACKAGE_NAME);
// Give WRITE_SMS AppOps permission to UID 1001 which contains multiple
// apps, all of them should be able to write to telephony provider.
// This is to allow the proxy package permission check in telephony provider
// to pass.
assignWriteSmsPermissionToSystemUid(appOps, Process.PHONE_UID);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData);
}
if (oldAppData != null && oldAppData.mSmsAppChangedReceiverClass != null) {
// Notify the old sms app that it's no longer the default
final Intent oldAppIntent =
new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
final ComponentName component = new ComponentName(oldAppData.mPackageName,
oldAppData.mSmsAppChangedReceiverClass);
oldAppIntent.setComponent(component);
oldAppIntent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, false);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldAppData.mPackageName);
}
context.sendBroadcast(oldAppIntent);
}
// Notify the new sms app that it's now the default (if the new sms app has a receiver
// to handle the changed default sms intent).
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "setDefaultApplicationInternal new applicationData=" +
applicationData);
}
if (applicationData.mSmsAppChangedReceiverClass != null) {
final Intent intent =
new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
final ComponentName component = new ComponentName(applicationData.mPackageName,
applicationData.mSmsAppChangedReceiverClass);
intent.setComponent(component);
intent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, true);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "setDefaultApplicationInternal new=" + packageName);
}
context.sendBroadcast(intent);
}
MetricsLogger.action(context, MetricsEvent.ACTION_DEFAULT_SMS_APP_CHANGED,
applicationData.mPackageName);
}
}
/**
* Assign WRITE_SMS AppOps permission to some special system apps.
*
* @param context The context
* @param packageManager The package manager instance
* @param appOps The AppOps manager instance
* @param packageName The package name of the system app
*/
private static void assignWriteSmsPermissionToSystemApp(Context context,
PackageManager packageManager, AppOpsManager appOps, String packageName) {
// First check package signature matches the caller's package signature.
// Since this class is only used internally by the system, this check makes sure
// the package signature matches system signature.
final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
if (result != PackageManager.SIGNATURE_MATCH) {
Rlog.e(LOG_TAG, packageName + " does not have system signature");
return;
}
try {
PackageInfo info = packageManager.getPackageInfo(packageName, 0);
int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
packageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)");
appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
packageName, AppOpsManager.MODE_ALLOWED);
}
} catch (NameNotFoundException e) {
// No whitelisted system app on this device
Rlog.e(LOG_TAG, "Package not found: " + packageName);
}
}
private static void assignWriteSmsPermissionToSystemUid(AppOpsManager appOps, int uid) {
appOps.setUidMode(AppOpsManager.OP_WRITE_SMS, uid, AppOpsManager.MODE_ALLOWED);
}
/**
* Tracks package changes and ensures that the default SMS app is always configured to be the
* preferred activity for SENDTO sms/mms intents.
*/
private static final class SmsPackageMonitor extends PackageMonitor {
final Context mContext;
public SmsPackageMonitor(Context context) {
super();
mContext = context;
}
@Override
public void onPackageDisappeared(String packageName, int reason) {
onPackageChanged();
}
@Override
public void onPackageAppeared(String packageName, int reason) {
onPackageChanged();
}
@Override
public void onPackageModified(String packageName) {
onPackageChanged();
}
private void onPackageChanged() {
PackageManager packageManager = mContext.getPackageManager();
Context userContext = mContext;
final int userId = getSendingUserId();
if (userId != UserHandle.USER_SYSTEM) {
try {
userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
new UserHandle(userId));
} catch (NameNotFoundException nnfe) {
if (DEBUG_MULTIUSER) {
Log.w(LOG_TAG, "Unable to create package context for user " + userId);
}
}
}
// Ensure this component is still configured as the preferred activity
ComponentName componentName = getDefaultSendToApplication(userContext, true);
if (componentName != null) {
configurePreferredActivity(packageManager, componentName, userId);
}
}
}
public static void initSmsPackageMonitor(Context context) {
sSmsPackageMonitor = new SmsPackageMonitor(context);
sSmsPackageMonitor.register(context, context.getMainLooper(), UserHandle.ALL, false);
}
private static void configurePreferredActivity(PackageManager packageManager,
ComponentName componentName, int userId) {
// Add the four activity preferences we want to direct to this app.
replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMS);
replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMSTO);
replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMS);
replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMSTO);
}
/**
* Updates the ACTION_SENDTO activity to the specified component for the specified scheme.
*/
private static void replacePreferredActivity(PackageManager packageManager,
ComponentName componentName, int userId, String scheme) {
// Build the set of existing activities that handle this scheme
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null));
List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivitiesAsUser(
intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER,
userId);
// Build the set of ComponentNames for these activities
final int n = resolveInfoList.size();
ComponentName[] set = new ComponentName[n];
for (int i = 0; i < n; i++) {
ResolveInfo info = resolveInfoList.get(i);
set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
}
// Update the preferred SENDTO activity for the specified scheme
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SENDTO);
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
intentFilter.addDataScheme(scheme);
packageManager.replacePreferredActivityAsUser(intentFilter,
IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
set, componentName, userId);
}
/**
* Returns SmsApplicationData for this package if this package is capable of being set as the
* default SMS application.
*/
public static SmsApplicationData getSmsApplicationData(String packageName, Context context) {
Collection<SmsApplicationData> applications = getApplicationCollection(context);
return getApplicationForPackage(applications, packageName);
}
/**
* Gets the default SMS application
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured.
* @return component name of the app and class to deliver SMS messages to
*/
public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
int userId = getIncomingUserId(context);
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
userId);
if (smsApplicationData != null) {
component = new ComponentName(smsApplicationData.mPackageName,
smsApplicationData.mSmsReceiverClass);
}
return component;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Gets the default MMS application
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured.
* @return component name of the app and class to deliver MMS messages to
*/
public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
int userId = getIncomingUserId(context);
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
userId);
if (smsApplicationData != null) {
component = new ComponentName(smsApplicationData.mPackageName,
smsApplicationData.mMmsReceiverClass);
}
return component;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Gets the default Respond Via Message application
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured.
* @return component name of the app and class to direct Respond Via Message intent to
*/
public static ComponentName getDefaultRespondViaMessageApplication(Context context,
boolean updateIfNeeded) {
int userId = getIncomingUserId(context);
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
userId);
if (smsApplicationData != null) {
component = new ComponentName(smsApplicationData.mPackageName,
smsApplicationData.mRespondViaMessageClass);
}
return component;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Gets the default Send To (smsto) application.
* <p>
* Caller must pass in the correct user context if calling from a singleton service.
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured.
* @return component name of the app and class to direct SEND_TO (smsto) intent to
*/
public static ComponentName getDefaultSendToApplication(Context context,
boolean updateIfNeeded) {
int userId = getIncomingUserId(context);
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
userId);
if (smsApplicationData != null) {
component = new ComponentName(smsApplicationData.mPackageName,
smsApplicationData.mSendToClass);
}
return component;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Gets the default application that handles external changes to the SmsProvider and
* MmsProvider.
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured.
* @return component name of the app and class to deliver change intents to
*/
public static ComponentName getDefaultExternalTelephonyProviderChangedApplication(
Context context, boolean updateIfNeeded) {
int userId = getIncomingUserId(context);
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
userId);
if (smsApplicationData != null
&& smsApplicationData.mProviderChangedReceiverClass != null) {
component = new ComponentName(smsApplicationData.mPackageName,
smsApplicationData.mProviderChangedReceiverClass);
}
return component;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Gets the default application that handles sim full event.
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured.
* @return component name of the app and class to deliver change intents to
*/
public static ComponentName getDefaultSimFullApplication(
Context context, boolean updateIfNeeded) {
int userId = getIncomingUserId(context);
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
userId);
if (smsApplicationData != null
&& smsApplicationData.mSimFullReceiverClass != null) {
component = new ComponentName(smsApplicationData.mPackageName,
smsApplicationData.mSimFullReceiverClass);
}
return component;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Returns whether need to write the SMS message to SMS database for this package.
* <p>
* Caller must pass in the correct user context if calling from a singleton service.
*/
public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
if (SmsManager.getDefault().getAutoPersisting()) {
return true;
}
return !isDefaultSmsApplication(context, packageName);
}
/**
* Check if a package is default sms app (or equivalent, like bluetooth)
*
* @param context context from the calling app
* @param packageName the name of the package to be checked
* @return true if the package is default sms app or bluetooth
*/
public static boolean isDefaultSmsApplication(Context context, String packageName) {
if (packageName == null) {
return false;
}
final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context);
if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName))
|| BLUETOOTH_PACKAGE_NAME.equals(packageName)) {
return true;
}
return false;
}
private static String getDefaultSmsApplicationPackageName(Context context) {
final ComponentName component = getDefaultSmsApplication(context, false);
if (component != null) {
return component.getPackageName();
}
return null;
}
}

View File

@@ -0,0 +1,310 @@
/*
* 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>() {
@Override
public SmsCbCmasInfo createFromParcel(Parcel in) {
return new SmsCbCmasInfo(in);
}
@Override
public SmsCbCmasInfo[] newArray(int size) {
return new SmsCbCmasInfo[size];
}
};
}

View File

@@ -0,0 +1,222 @@
/*
* 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.uicc.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;
/** Whether ETWS primary message or not/ */
private final boolean mPrimary;
/**
* 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,
boolean primary, byte[] warningSecurityInformation) {
mWarningType = warningType;
mEmergencyUserAlert = emergencyUserAlert;
mActivatePopup = activatePopup;
mPrimary = primary;
mWarningSecurityInformation = warningSecurityInformation;
}
/** Create a new SmsCbEtwsInfo object from a Parcel. */
SmsCbEtwsInfo(Parcel in) {
mWarningType = in.readInt();
mEmergencyUserAlert = (in.readInt() != 0);
mActivatePopup = (in.readInt() != 0);
mPrimary = (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.writeInt(mPrimary ? 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 ETWS format flag.
* @return true if the message is primary message, otherwise secondary message
*/
public boolean isPrimary() {
return mPrimary;
}
/**
* 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) - 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>() {
@Override
public SmsCbEtwsInfo createFromParcel(Parcel in) {
return new SmsCbEtwsInfo(in);
}
@Override
public SmsCbEtwsInfo[] newArray(int size) {
return new SmsCbEtwsInfo[size];
}
};
}

View File

@@ -0,0 +1,200 @@
/*
* 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;
/**
* 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;
}
}

View File

@@ -0,0 +1,382 @@
/*
* Copyright (C) 2010 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;
/**
* Parcelable object containing a received cell broadcast message. There are four different types
* of Cell Broadcast messages:
*
* <ul>
* <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li>
* <li>cell information messages, broadcast on channel 50, indicating the current cell name for
* roaming purposes (required to display on the idle screen in Brazil)</li>
* <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li>
* <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li>
* </ul>
*
* <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
* UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
* unified under a common name, avoiding some names, such as "Message Identifier", that refer to
* two completely different concepts in 3GPP and CDMA.
*
* <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
* of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
* and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
* application should
*
* <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
* to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
* unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
* Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
* are considered unique to the PLMN, to the current cell, or to the current Location Area (or
* Service Area in UMTS). The relevant values are concatenated into a single String which will be
* unique if the messages are not duplicates.
*
* <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
* pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
*
* <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
* {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
* Only system applications such as the CellBroadcastReceiver may receive notifications for
* emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
* interference with the immediate display of the alert message and playing of the alert sound and
* vibration pattern, which could be caused by poorly written or malicious non-system code.
*
* @hide
*/
public class SmsCbMessage implements Parcelable {
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 (GSM/UMTS and all CDMA broadcasts). */
public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
/** Location / service area wide geographical scope (GSM/UMTS only). */
public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
/** 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;
/**
* 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 dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written (ignored).
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mMessageFormat);
dest.writeInt(mGeographicalScope);
dest.writeInt(mSerialNumber);
mLocation.writeToParcel(dest, flags);
dest.writeInt(mServiceCategory);
dest.writeString(mLanguage);
dest.writeString(mBody);
dest.writeInt(mPriority);
if (mEtwsWarningInfo != null) {
// parcel ETWS warning information
dest.writeInt('E');
mEtwsWarningInfo.writeToParcel(dest, flags);
} else if (mCmasWarningInfo != null) {
// parcel CMAS warning information
dest.writeInt('C');
mCmasWarningInfo.writeToParcel(dest, flags);
} else {
// 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 (GSM/UMTS only).
*
* @return Geographical scope
*/
public int getGeographicalScope() {
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;
}
/**
* Get the ISO-639-1 language code for this message, or null if unspecified
*
* @return Language code
*/
public String getLanguageCode() {
return mLanguage;
}
/**
* Get the body of this message, or null if no body available
*
* @return Body, or null
*/
public String getMessageBody() {
return mBody;
}
/**
* 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 mMessageFormat;
}
/**
* 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 int getMessagePriority() {
return mPriority;
}
/**
* If this is an ETWS warning notification then this method will return an object containing
* the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an
* ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte
* digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the
* ETWS primary notification timestamp and digital signature if received.
*
* @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
*/
public SmsCbEtwsInfo getEtwsWarningInfo() {
return mEtwsWarningInfo;
}
/**
* If this is a CMAS warning notification then this method will return an object containing
* the CMAS message class, category, response type, severity, urgency and certainty.
* The message class is always present. Severity, urgency and certainty are present for CDMA
* warning notifications containing a type 1 elements record and for GSM and UMTS warnings
* except for the Presidential-level alert category. Category and response type are only
* available for CDMA notifications containing a type 1 elements record.
*
* @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
*/
public SmsCbCmasInfo getCmasWarningInfo() {
return mCmasWarningInfo;
}
/**
* Return whether this message is an emergency (PWS) message type.
* @return true if the message is a public warning notification; false otherwise
*/
public boolean isEmergencyMessage() {
return mPriority == MESSAGE_PRIORITY_EMERGENCY;
}
/**
* Return whether this message is an ETWS warning alert.
* @return true if the message is an ETWS warning notification; false otherwise
*/
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{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;
}
}

View File

@@ -0,0 +1,314 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.util.HexDump;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
/**
* SMS user data header, as specified in TS 23.040 9.2.3.24.
*/
public class SmsHeader {
// TODO(cleanup): this data structure is generally referred to as
// the 'user data header' or UDH, and so the class name should
// change to reflect this...
/** SMS user data header information element identifiers.
* (see TS 23.040 9.2.3.24)
*/
public static final int ELT_ID_CONCATENATED_8_BIT_REFERENCE = 0x00;
public static final int ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION = 0x01;
public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT = 0x04;
public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT = 0x05;
public static final int ELT_ID_SMSC_CONTROL_PARAMS = 0x06;
public static final int ELT_ID_UDH_SOURCE_INDICATION = 0x07;
public static final int ELT_ID_CONCATENATED_16_BIT_REFERENCE = 0x08;
public static final int ELT_ID_WIRELESS_CTRL_MSG_PROTOCOL = 0x09;
public static final int ELT_ID_TEXT_FORMATTING = 0x0A;
public static final int ELT_ID_PREDEFINED_SOUND = 0x0B;
public static final int ELT_ID_USER_DEFINED_SOUND = 0x0C;
public static final int ELT_ID_PREDEFINED_ANIMATION = 0x0D;
public static final int ELT_ID_LARGE_ANIMATION = 0x0E;
public static final int ELT_ID_SMALL_ANIMATION = 0x0F;
public static final int ELT_ID_LARGE_PICTURE = 0x10;
public static final int ELT_ID_SMALL_PICTURE = 0x11;
public static final int ELT_ID_VARIABLE_PICTURE = 0x12;
public static final int ELT_ID_USER_PROMPT_INDICATOR = 0x13;
public static final int ELT_ID_EXTENDED_OBJECT = 0x14;
public static final int ELT_ID_REUSED_EXTENDED_OBJECT = 0x15;
public static final int ELT_ID_COMPRESSION_CONTROL = 0x16;
public static final int ELT_ID_OBJECT_DISTR_INDICATOR = 0x17;
public static final int ELT_ID_STANDARD_WVG_OBJECT = 0x18;
public static final int ELT_ID_CHARACTER_SIZE_WVG_OBJECT = 0x19;
public static final int ELT_ID_EXTENDED_OBJECT_DATA_REQUEST_CMD = 0x1A;
public static final int ELT_ID_RFC_822_EMAIL_HEADER = 0x20;
public static final int ELT_ID_HYPERLINK_FORMAT_ELEMENT = 0x21;
public static final int ELT_ID_REPLY_ADDRESS_ELEMENT = 0x22;
public static final int ELT_ID_ENHANCED_VOICE_MAIL_INFORMATION = 0x23;
public static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24;
public static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25;
public static final int PORT_WAP_PUSH = 2948;
public static final int PORT_WAP_WSP = 9200;
public static class PortAddrs {
public int destPort;
public int origPort;
public boolean areEightBits;
}
public static class ConcatRef {
public int refNumber;
public int seqNumber;
public int msgCount;
public boolean isEightBits;
}
public static class SpecialSmsMsg {
public int msgIndType;
public int msgCount;
}
/**
* A header element that is not explicitly parsed, meaning not
* PortAddrs or ConcatRef or SpecialSmsMsg.
*/
public static class MiscElt {
public int id;
public byte[] data;
}
public PortAddrs portAddrs;
public ConcatRef concatRef;
public ArrayList<SpecialSmsMsg> specialSmsMsgList = new ArrayList<SpecialSmsMsg>();
public ArrayList<MiscElt> miscEltList = new ArrayList<MiscElt>();
/** 7 bit national language locking shift table, or 0 for GSM default 7 bit alphabet. */
public int languageTable;
/** 7 bit national language single shift table, or 0 for GSM default 7 bit extension table. */
public int languageShiftTable;
public SmsHeader() {}
/**
* Create structured SmsHeader object from serialized byte array representation.
* (see TS 23.040 9.2.3.24)
* @param data is user data header bytes
* @return SmsHeader object
*/
public static SmsHeader fromByteArray(byte[] data) {
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
SmsHeader smsHeader = new SmsHeader();
while (inStream.available() > 0) {
/**
* NOTE: as defined in the spec, ConcatRef and PortAddr
* fields should not reoccur, but if they do the last
* occurrence is to be used. Also, for ConcatRef
* elements, if the count is zero, sequence is zero, or
* sequence is larger than count, the entire element is to
* be ignored.
*/
int id = inStream.read();
int length = inStream.read();
ConcatRef concatRef;
PortAddrs portAddrs;
switch (id) {
case ELT_ID_CONCATENATED_8_BIT_REFERENCE:
concatRef = new ConcatRef();
concatRef.refNumber = inStream.read();
concatRef.msgCount = inStream.read();
concatRef.seqNumber = inStream.read();
concatRef.isEightBits = true;
if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
concatRef.seqNumber <= concatRef.msgCount) {
smsHeader.concatRef = concatRef;
}
break;
case ELT_ID_CONCATENATED_16_BIT_REFERENCE:
concatRef = new ConcatRef();
concatRef.refNumber = (inStream.read() << 8) | inStream.read();
concatRef.msgCount = inStream.read();
concatRef.seqNumber = inStream.read();
concatRef.isEightBits = false;
if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
concatRef.seqNumber <= concatRef.msgCount) {
smsHeader.concatRef = concatRef;
}
break;
case ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT:
portAddrs = new PortAddrs();
portAddrs.destPort = inStream.read();
portAddrs.origPort = inStream.read();
portAddrs.areEightBits = true;
smsHeader.portAddrs = portAddrs;
break;
case ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT:
portAddrs = new PortAddrs();
portAddrs.destPort = (inStream.read() << 8) | inStream.read();
portAddrs.origPort = (inStream.read() << 8) | inStream.read();
portAddrs.areEightBits = false;
smsHeader.portAddrs = portAddrs;
break;
case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT:
smsHeader.languageShiftTable = inStream.read();
break;
case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
smsHeader.languageTable = inStream.read();
break;
case ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION:
SpecialSmsMsg specialSmsMsg = new SpecialSmsMsg();
specialSmsMsg.msgIndType = inStream.read();
specialSmsMsg.msgCount = inStream.read();
smsHeader.specialSmsMsgList.add(specialSmsMsg);
break;
default:
MiscElt miscElt = new MiscElt();
miscElt.id = id;
miscElt.data = new byte[length];
inStream.read(miscElt.data, 0, length);
smsHeader.miscEltList.add(miscElt);
}
}
return smsHeader;
}
/**
* Create serialized byte array representation from structured SmsHeader object.
* (see TS 23.040 9.2.3.24)
* @return Byte array representing the SmsHeader
*/
public static byte[] toByteArray(SmsHeader smsHeader) {
if ((smsHeader.portAddrs == null) &&
(smsHeader.concatRef == null) &&
(smsHeader.specialSmsMsgList.isEmpty()) &&
(smsHeader.miscEltList.isEmpty()) &&
(smsHeader.languageShiftTable == 0) &&
(smsHeader.languageTable == 0)) {
return null;
}
ByteArrayOutputStream outStream =
new ByteArrayOutputStream(SmsConstants.MAX_USER_DATA_BYTES);
ConcatRef concatRef = smsHeader.concatRef;
if (concatRef != null) {
if (concatRef.isEightBits) {
outStream.write(ELT_ID_CONCATENATED_8_BIT_REFERENCE);
outStream.write(3);
outStream.write(concatRef.refNumber);
} else {
outStream.write(ELT_ID_CONCATENATED_16_BIT_REFERENCE);
outStream.write(4);
outStream.write(concatRef.refNumber >>> 8);
outStream.write(concatRef.refNumber & 0x00FF);
}
outStream.write(concatRef.msgCount);
outStream.write(concatRef.seqNumber);
}
PortAddrs portAddrs = smsHeader.portAddrs;
if (portAddrs != null) {
if (portAddrs.areEightBits) {
outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT);
outStream.write(2);
outStream.write(portAddrs.destPort);
outStream.write(portAddrs.origPort);
} else {
outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT);
outStream.write(4);
outStream.write(portAddrs.destPort >>> 8);
outStream.write(portAddrs.destPort & 0x00FF);
outStream.write(portAddrs.origPort >>> 8);
outStream.write(portAddrs.origPort & 0x00FF);
}
}
if (smsHeader.languageShiftTable != 0) {
outStream.write(ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT);
outStream.write(1);
outStream.write(smsHeader.languageShiftTable);
}
if (smsHeader.languageTable != 0) {
outStream.write(ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT);
outStream.write(1);
outStream.write(smsHeader.languageTable);
}
for (SpecialSmsMsg specialSmsMsg : smsHeader.specialSmsMsgList) {
outStream.write(ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION);
outStream.write(2);
outStream.write(specialSmsMsg.msgIndType & 0xFF);
outStream.write(specialSmsMsg.msgCount & 0xFF);
}
for (MiscElt miscElt : smsHeader.miscEltList) {
outStream.write(miscElt.id);
outStream.write(miscElt.data.length);
outStream.write(miscElt.data, 0, miscElt.data.length);
}
return outStream.toByteArray();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("UserDataHeader ");
builder.append("{ ConcatRef ");
if (concatRef == null) {
builder.append("unset");
} else {
builder.append("{ refNumber=" + concatRef.refNumber);
builder.append(", msgCount=" + concatRef.msgCount);
builder.append(", seqNumber=" + concatRef.seqNumber);
builder.append(", isEightBits=" + concatRef.isEightBits);
builder.append(" }");
}
builder.append(", PortAddrs ");
if (portAddrs == null) {
builder.append("unset");
} else {
builder.append("{ destPort=" + portAddrs.destPort);
builder.append(", origPort=" + portAddrs.origPort);
builder.append(", areEightBits=" + portAddrs.areEightBits);
builder.append(" }");
}
if (languageShiftTable != 0) {
builder.append(", languageShiftTable=" + languageShiftTable);
}
if (languageTable != 0) {
builder.append(", languageTable=" + languageTable);
}
for (SpecialSmsMsg specialSmsMsg : specialSmsMsgList) {
builder.append(", SpecialSmsMsg ");
builder.append("{ msgIndType=" + specialSmsMsg.msgIndType);
builder.append(", msgCount=" + specialSmsMsg.msgCount);
builder.append(" }");
}
for (MiscElt miscElt : miscEltList) {
builder.append(", MiscElt ");
builder.append("{ id=" + miscElt.id);
builder.append(", length=" + miscElt.data.length);
builder.append(", data=" + HexDump.toHexString(miscElt.data));
builder.append(" }");
}
builder.append(" }");
return builder.toString();
}
}

View File

@@ -0,0 +1,433 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SmsHeader;
import java.text.BreakIterator;
import java.util.Arrays;
import android.provider.Telephony;
import android.telephony.SmsMessage;
import android.text.Emoji;
/**
* Base class declaring the specific methods and members for SmsMessage.
* {@hide}
*/
public abstract class SmsMessageBase {
/** {@hide} The address of the SMSC. May be null */
protected String mScAddress;
/** {@hide} The address of the sender */
protected SmsAddress mOriginatingAddress;
/** {@hide} The message body as a string. May be null if the message isn't text */
protected String mMessageBody;
/** {@hide} */
protected String mPseudoSubject;
/** {@hide} Non-null if this is an email gateway message */
protected String mEmailFrom;
/** {@hide} Non-null if this is an email gateway message */
protected String mEmailBody;
/** {@hide} */
protected boolean mIsEmail;
/** {@hide} Time when SC (service centre) received the message */
protected long mScTimeMillis;
/** {@hide} The raw PDU of the message */
protected byte[] mPdu;
/** {@hide} The raw bytes for the user data section of the message */
protected byte[] mUserData;
/** {@hide} */
protected SmsHeader mUserDataHeader;
// "Message Waiting Indication Group"
// 23.038 Section 4
/** {@hide} */
protected boolean mIsMwi;
/** {@hide} */
protected boolean mMwiSense;
/** {@hide} */
protected boolean mMwiDontStore;
/**
* Indicates status for messages stored on the ICC.
*/
protected int mStatusOnIcc = -1;
/**
* Record index of message in the EF.
*/
protected int mIndexOnIcc = -1;
/** TP-Message-Reference - Message Reference of sent message. @hide */
public int mMessageRef;
// TODO(): This class is duplicated in SmsMessage.java. Refactor accordingly.
public static abstract class SubmitPduBase {
public byte[] encodedScAddress; // Null if not applicable.
public byte[] encodedMessage;
@Override
public String toString() {
return "SubmitPdu: encodedScAddress = "
+ Arrays.toString(encodedScAddress)
+ ", encodedMessage = "
+ Arrays.toString(encodedMessage);
}
}
/**
* Returns the address of the SMS service center that relayed this message
* or null if there is none.
*/
public String getServiceCenterAddress() {
return mScAddress;
}
/**
* Returns the originating address (sender) of this SMS message in String
* form or null if unavailable
*/
public String getOriginatingAddress() {
if (mOriginatingAddress == null) {
return null;
}
return mOriginatingAddress.getAddressString();
}
/**
* Returns the originating address, or email from address if this message
* was from an email gateway. Returns null if originating address
* unavailable.
*/
public String getDisplayOriginatingAddress() {
if (mIsEmail) {
return mEmailFrom;
} else {
return getOriginatingAddress();
}
}
/**
* Returns the message body as a String, if it exists and is text based.
* @return message body is there is one, otherwise null
*/
public String getMessageBody() {
return mMessageBody;
}
/**
* Returns the class of this message.
*/
public abstract SmsConstants.MessageClass getMessageClass();
/**
* Returns the message body, or email message body if this message was from
* an email gateway. Returns null if message body unavailable.
*/
public String getDisplayMessageBody() {
if (mIsEmail) {
return mEmailBody;
} else {
return getMessageBody();
}
}
/**
* Unofficial convention of a subject line enclosed in parens empty string
* if not present
*/
public String getPseudoSubject() {
return mPseudoSubject == null ? "" : mPseudoSubject;
}
/**
* Returns the service centre timestamp in currentTimeMillis() format
*/
public long getTimestampMillis() {
return mScTimeMillis;
}
/**
* Returns true if message is an email.
*
* @return true if this message came through an email gateway and email
* sender / subject / parsed body are available
*/
public boolean isEmail() {
return mIsEmail;
}
/**
* @return if isEmail() is true, body of the email sent through the gateway.
* null otherwise
*/
public String getEmailBody() {
return mEmailBody;
}
/**
* @return if isEmail() is true, email from address of email sent through
* the gateway. null otherwise
*/
public String getEmailFrom() {
return mEmailFrom;
}
/**
* Get protocol identifier.
*/
public abstract int getProtocolIdentifier();
/**
* See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
* SMS
*/
public abstract boolean isReplace();
/**
* Returns true for CPHS MWI toggle message.
*
* @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
* B.4.2
*/
public abstract boolean isCphsMwiMessage();
/**
* returns true if this message is a CPHS voicemail / message waiting
* indicator (MWI) clear message
*/
public abstract boolean isMWIClearMessage();
/**
* returns true if this message is a CPHS voicemail / message waiting
* indicator (MWI) set message
*/
public abstract boolean isMWISetMessage();
/**
* returns true if this message is a "Message Waiting Indication Group:
* Discard Message" notification and should not be stored.
*/
public abstract boolean isMwiDontStore();
/**
* returns the user data section minus the user data header if one was
* present.
*/
public byte[] getUserData() {
return mUserData;
}
/**
* Returns an object representing the user data header
*
* {@hide}
*/
public SmsHeader getUserDataHeader() {
return mUserDataHeader;
}
/**
* TODO(cleanup): The term PDU is used in a seemingly non-unique
* manner -- for example, what is the difference between this byte
* array and the contents of SubmitPdu objects. Maybe a more
* illustrative term would be appropriate.
*/
/**
* Returns the raw PDU for the message.
*/
public byte[] getPdu() {
return mPdu;
}
/**
* For an SMS-STATUS-REPORT message, this returns the status field from
* the status report. This field indicates the status of a previously
* submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a
* description of values.
*
* @return 0 indicates the previously sent message was received.
* See TS 23.040, 9.9.2.3.15 for a description of other possible
* values.
*/
public abstract int getStatus();
/**
* Return true iff the message is a SMS-STATUS-REPORT message.
*/
public abstract boolean isStatusReportMessage();
/**
* Returns true iff the <code>TP-Reply-Path</code> bit is set in
* this message.
*/
public abstract boolean isReplyPathPresent();
/**
* Returns the status of the message on the ICC (read, unread, sent, unsent).
*
* @return the status of the message on the ICC. These are:
* SmsManager.STATUS_ON_ICC_FREE
* SmsManager.STATUS_ON_ICC_READ
* SmsManager.STATUS_ON_ICC_UNREAD
* SmsManager.STATUS_ON_ICC_SEND
* SmsManager.STATUS_ON_ICC_UNSENT
*/
public int getStatusOnIcc() {
return mStatusOnIcc;
}
/**
* Returns the record index of the message on the ICC (1-based index).
* @return the record index of the message on the ICC, or -1 if this
* SmsMessage was not created from a ICC SMS EF record.
*/
public int getIndexOnIcc() {
return mIndexOnIcc;
}
protected void parseMessageBody() {
// originatingAddress could be null if this message is from a status
// report.
if (mOriginatingAddress != null && mOriginatingAddress.couldBeEmailGateway()) {
extractEmailAddressFromMessageBody();
}
}
/**
* Try to parse this message as an email gateway message
* There are two ways specified in TS 23.040 Section 3.8 :
* - SMS message "may have its TP-PID set for Internet electronic mail - MT
* SMS format: [<from-address><space>]<message> - "Depending on the
* nature of the gateway, the destination/origination address is either
* derived from the content of the SMS TP-OA or TP-DA field, or the
* TP-OA/TP-DA field contains a generic gateway address and the to/from
* address is added at the beginning as shown above." (which is supported here)
* - Multiple addresses separated by commas, no spaces, Subject field delimited
* by '()' or '##' and '#' Section 9.2.3.24.11 (which are NOT supported here)
*/
protected void extractEmailAddressFromMessageBody() {
/* Some carriers may use " /" delimiter as below
*
* 1. [x@y][ ]/[subject][ ]/[body]
* -or-
* 2. [x@y][ ]/[body]
*/
String[] parts = mMessageBody.split("( /)|( )", 2);
if (parts.length < 2) return;
mEmailFrom = parts[0];
mEmailBody = parts[1];
mIsEmail = Telephony.Mms.isEmailAddress(mEmailFrom);
}
/**
* Find the next position to start a new fragment of a multipart SMS.
*
* @param currentPosition current start position of the fragment
* @param byteLimit maximum number of bytes in the fragment
* @param msgBody text of the SMS in UTF-16 encoding
* @return the position to start the next fragment
*/
public static int findNextUnicodePosition(
int currentPosition, int byteLimit, CharSequence msgBody) {
int nextPos = Math.min(currentPosition + byteLimit / 2, msgBody.length());
// Check whether the fragment ends in a character boundary. Some characters take 4-bytes
// in UTF-16 encoding. Many carriers cannot handle
// a fragment correctly if it does not end at a character boundary.
if (nextPos < msgBody.length()) {
BreakIterator breakIterator = BreakIterator.getCharacterInstance();
breakIterator.setText(msgBody.toString());
if (!breakIterator.isBoundary(nextPos)) {
int breakPos = breakIterator.preceding(nextPos);
while (breakPos + 4 <= nextPos
&& Emoji.isRegionalIndicatorSymbol(
Character.codePointAt(msgBody, breakPos))
&& Emoji.isRegionalIndicatorSymbol(
Character.codePointAt(msgBody, breakPos + 2))) {
// skip forward over flags (pairs of Regional Indicator Symbol)
breakPos += 4;
}
if (breakPos > currentPosition) {
nextPos = breakPos;
} else if (Character.isHighSurrogate(msgBody.charAt(nextPos - 1))) {
// no character boundary in this fragment, try to at least land on a code point
nextPos -= 1;
}
}
}
return nextPos;
}
/**
* Calculate the TextEncodingDetails of a message encoded in Unicode.
*/
public static TextEncodingDetails calcUnicodeEncodingDetails(CharSequence msgBody) {
TextEncodingDetails ted = new TextEncodingDetails();
int octets = msgBody.length() * 2;
ted.codeUnitSize = SmsConstants.ENCODING_16BIT;
ted.codeUnitCount = msgBody.length();
if (octets > SmsConstants.MAX_USER_DATA_BYTES) {
// If EMS is not supported, break down EMS into single segment SMS
// and add page info " x/y".
// In the case of UCS2 encoding type, we need 8 bytes for this
// but we only have 6 bytes from UDH, so truncate the limit for
// each segment by 2 bytes (1 char).
int maxUserDataBytesWithHeader = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
if (!SmsMessage.hasEmsSupport()) {
// make sure total number of segments is less than 10
if (octets <= 9 * (maxUserDataBytesWithHeader - 2)) {
maxUserDataBytesWithHeader -= 2;
}
}
int pos = 0; // Index in code units.
int msgCount = 0;
while (pos < msgBody.length()) {
int nextPos = findNextUnicodePosition(pos, maxUserDataBytesWithHeader,
msgBody);
if (nextPos == msgBody.length()) {
ted.codeUnitsRemaining = pos + maxUserDataBytesWithHeader / 2 -
msgBody.length();
}
pos = nextPos;
msgCount++;
}
ted.msgCount = msgCount;
} else {
ted.msgCount = 1;
ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets) / 2;
}
return ted;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,228 @@
/*
* Copyright (C) 2008 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.sms;
import android.util.SparseBooleanArray;
import com.android.internal.telephony.SmsAddress;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.util.HexDump;
public class CdmaSmsAddress extends SmsAddress {
/**
* Digit Mode Indicator is a 1-bit value that indicates whether
* the address digits are 4-bit DTMF codes or 8-bit codes. (See
* 3GPP2 C.S0015-B, v2, 3.4.3.3)
*/
static public final int DIGIT_MODE_4BIT_DTMF = 0x00;
static public final int DIGIT_MODE_8BIT_CHAR = 0x01;
public int digitMode;
/**
* Number Mode Indicator is 1-bit value that indicates whether the
* address type is a data network address or not. (See 3GPP2
* C.S0015-B, v2, 3.4.3.3)
*/
static public final int NUMBER_MODE_NOT_DATA_NETWORK = 0x00;
static public final int NUMBER_MODE_DATA_NETWORK = 0x01;
public int numberMode;
/**
* Number Types for data networks.
* (See 3GPP2 C.S005-D, table2.7.1.3.2.4-2 for complete table)
* (See 3GPP2 C.S0015-B, v2, 3.4.3.3 for data network subset)
* NOTE: value is stored in the parent class ton field.
*/
static public final int TON_UNKNOWN = 0x00;
static public final int TON_INTERNATIONAL_OR_IP = 0x01;
static public final int TON_NATIONAL_OR_EMAIL = 0x02;
static public final int TON_NETWORK = 0x03;
static public final int TON_SUBSCRIBER = 0x04;
static public final int TON_ALPHANUMERIC = 0x05;
static public final int TON_ABBREVIATED = 0x06;
static public final int TON_RESERVED = 0x07;
/**
* Maximum lengths for fields as defined in ril_cdma_sms.h.
*/
static public final int SMS_ADDRESS_MAX = 36;
static public final int SMS_SUBADDRESS_MAX = 36;
/**
* This field shall be set to the number of address digits
* (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
*/
public int numberOfDigits;
/**
* Numbering Plan identification is a 0 or 4-bit value that
* indicates which numbering plan identification is set. (See
* 3GPP2, C.S0015-B, v2, 3.4.3.3 and C.S005-D, table2.7.1.3.2.4-3)
*/
static public final int NUMBERING_PLAN_UNKNOWN = 0x0;
static public final int NUMBERING_PLAN_ISDN_TELEPHONY = 0x1;
//static protected final int NUMBERING_PLAN_DATA = 0x3;
//static protected final int NUMBERING_PLAN_TELEX = 0x4;
//static protected final int NUMBERING_PLAN_PRIVATE = 0x9;
public int numberPlan;
/**
* NOTE: the parsed string address and the raw byte array values
* are stored in the parent class address and origBytes fields,
* respectively.
*/
public CdmaSmsAddress(){
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("CdmaSmsAddress ");
builder.append("{ digitMode=" + digitMode);
builder.append(", numberMode=" + numberMode);
builder.append(", numberPlan=" + numberPlan);
builder.append(", numberOfDigits=" + numberOfDigits);
builder.append(", ton=" + ton);
builder.append(", address=\"" + address + "\"");
builder.append(", origBytes=" + HexDump.toHexString(origBytes));
builder.append(" }");
return builder.toString();
}
/*
* TODO(cleanup): Refactor the parsing for addresses to better
* share code and logic with GSM. Also, gather all DTMF/BCD
* processing code in one place.
*/
private static byte[] parseToDtmf(String address) {
int digits = address.length();
byte[] result = new byte[digits];
for (int i = 0; i < digits; i++) {
char c = address.charAt(i);
int val = 0;
if ((c >= '1') && (c <= '9')) val = c - '0';
else if (c == '0') val = 10;
else if (c == '*') val = 11;
else if (c == '#') val = 12;
else return null;
result[i] = (byte)val;
}
return result;
}
private static final char[] numericCharsDialable = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#'
};
private static final char[] numericCharsSugar = {
'(', ')', ' ', '-', '+', '.', '/', '\\'
};
private static final SparseBooleanArray numericCharDialableMap = new SparseBooleanArray (
numericCharsDialable.length + numericCharsSugar.length);
static {
for (int i = 0; i < numericCharsDialable.length; i++) {
numericCharDialableMap.put(numericCharsDialable[i], true);
}
for (int i = 0; i < numericCharsSugar.length; i++) {
numericCharDialableMap.put(numericCharsSugar[i], false);
}
}
/**
* Given a numeric address string, return the string without
* syntactic sugar, meaning parens, spaces, hyphens/minuses, or
* plus signs. If the input string contains non-numeric
* non-punctuation characters, return null.
*/
private static String filterNumericSugar(String address) {
StringBuilder builder = new StringBuilder();
int len = address.length();
for (int i = 0; i < len; i++) {
char c = address.charAt(i);
int mapIndex = numericCharDialableMap.indexOfKey(c);
if (mapIndex < 0) return null;
if (! numericCharDialableMap.valueAt(mapIndex)) continue;
builder.append(c);
}
return builder.toString();
}
/**
* Given a string, return the string without whitespace,
* including CR/LF.
*/
private static String filterWhitespace(String address) {
StringBuilder builder = new StringBuilder();
int len = address.length();
for (int i = 0; i < len; i++) {
char c = address.charAt(i);
if ((c == ' ') || (c == '\r') || (c == '\n') || (c == '\t')) continue;
builder.append(c);
}
return builder.toString();
}
/**
* Given a string, create a corresponding CdmaSmsAddress object.
*
* The result will be null if the input string is not
* representable using printable ASCII.
*
* For numeric addresses, the string is cleaned up by removing
* common punctuation. For alpha addresses, the string is cleaned
* up by removing whitespace.
*/
public static CdmaSmsAddress parse(String address) {
CdmaSmsAddress addr = new CdmaSmsAddress();
addr.address = address;
addr.ton = CdmaSmsAddress.TON_UNKNOWN;
byte[] origBytes = null;
String filteredAddr = filterNumericSugar(address);
if (filteredAddr != null) {
origBytes = parseToDtmf(filteredAddr);
}
if (origBytes != null) {
addr.digitMode = DIGIT_MODE_4BIT_DTMF;
addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
if (address.indexOf('+') != -1) {
addr.ton = TON_INTERNATIONAL_OR_IP;
}
} else {
filteredAddr = filterWhitespace(address);
origBytes = UserData.stringToAscii(filteredAddr);
if (origBytes == null) {
return null;
}
addr.digitMode = DIGIT_MODE_8BIT_CHAR;
addr.numberMode = NUMBER_MODE_DATA_NETWORK;
if (address.indexOf('@') != -1) {
addr.ton = TON_NATIONAL_OR_EMAIL;
}
}
addr.origBytes = origBytes;
addr.numberOfDigits = origBytes.length;
return addr;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2010 The Android Open Source Project. All rights reserved.
*
* 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.sms;
public class CdmaSmsSubaddress {
public int type;
public byte odd;
public byte[] origBytes;
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2008 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.sms;
import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
public final class SmsEnvelope {
/**
* Message Types
* (See 3GPP2 C.S0015-B 3.4.1)
*/
static public final int MESSAGE_TYPE_POINT_TO_POINT = 0x00;
static public final int MESSAGE_TYPE_BROADCAST = 0x01;
static public final int MESSAGE_TYPE_ACKNOWLEDGE = 0x02;
/**
* Supported Teleservices
* (See 3GPP2 N.S0005 and TIA-41)
*/
static public final int TELESERVICE_NOT_SET = 0x0000;
static public final int TELESERVICE_WMT = 0x1002;
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
*/
// Voice mail notification through Message Waiting Indication in CDMA mode or Analog mode.
// Defined in 3GPP2 C.S-0005, 3.7.5.6, an Info Record containing an 8-bit number with the
// number of messages waiting, it's used by some CDMA carriers for a voice mail count.
static public final int TELESERVICE_MWI = 0x40000;
// Service Categories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
// static final int SERVICE_CATEGORY_EMERGENCY = 0x0001;
//...
// 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
*/
public int messageType;
/**
* The 16-bit Teleservice parameter identifies which upper layer service access point is sending
* or receiving the message.
* (See 3GPP2 C.S0015-B, v2, 3.4.3.1)
*/
public int teleService = TELESERVICE_NOT_SET;
/**
* The 16-bit service category parameter identifies the type of service provided
* by the SMS message.
* (See 3GPP2 C.S0015-B, v2, 3.4.3.2)
*/
public int serviceCategory;
/**
* The origination address identifies the originator of the SMS message.
* (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
*/
public CdmaSmsAddress origAddress;
/**
* The destination address identifies the target of the SMS message.
* (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
*/
public CdmaSmsAddress destAddress;
/**
* The origination subaddress identifies the originator of the SMS message.
* (See 3GPP2 C.S0015-B, v2, 3.4.3.4)
*/
public CdmaSmsSubaddress origSubaddress;
/**
* The 6-bit bearer reply parameter is used to request the return of a
* SMS Acknowledge Message.
* (See 3GPP2 C.S0015-B, v2, 3.4.3.5)
*/
public int bearerReply;
/**
* Cause Code values:
* The cause code parameters are an indication whether an SMS error has occurred and if so,
* whether the condition is considered temporary or permanent.
* ReplySeqNo 6-bit value,
* ErrorClass 2-bit value,
* CauseCode 0-bit or 8-bit value
* (See 3GPP2 C.S0015-B, v2, 3.4.3.6)
*/
public byte replySeqNo;
public byte errorClass;
public byte causeCode;
/**
* encoded bearer data
* (See 3GPP2 C.S0015-B, v2, 3.4.3.7)
*/
public byte[] bearerData;
public SmsEnvelope() {
// nothing to see here
}
}

View File

@@ -0,0 +1,968 @@
/*
* Copyright (C) 2008 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.os.SystemProperties;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaSmsCbProgramData;
import android.telephony.Rlog;
import android.util.Log;
import android.text.TextUtils;
import android.content.res.Resources;
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import com.android.internal.telephony.SmsAddress;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.cdma.sms.BearerData;
import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
import com.android.internal.telephony.cdma.sms.SmsEnvelope;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.telephony.uicc.IccUtils;
import com.android.internal.util.BitwiseInputStream;
import com.android.internal.util.HexDump;
import com.android.internal.telephony.Sms7BitEncodingTranslator;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
/**
* TODO(cleanup): these constants are disturbing... are they not just
* different interpretations on one number? And if we did not have
* terrible class name overlap, they would not need to be directly
* imported like this. The class in this file could just as well be
* named CdmaSmsMessage, could it not?
*/
/**
* TODO(cleanup): internally returning null in many places makes
* debugging very hard (among many other reasons) and should be made
* more meaningful (replaced with exceptions for example). Null
* returns should only occur at the very outside of the module/class
* scope.
*/
/**
* A Short Message Service message.
*
*/
public class SmsMessage extends SmsMessageBase {
static final String LOG_TAG = "SmsMessage";
static private final String LOGGABLE_TAG = "CDMA:SMS";
private static final boolean VDBG = false;
private final static byte TELESERVICE_IDENTIFIER = 0x00;
private final static byte SERVICE_CATEGORY = 0x01;
private final static byte ORIGINATING_ADDRESS = 0x02;
private final static byte ORIGINATING_SUB_ADDRESS = 0x03;
private final static byte DESTINATION_ADDRESS = 0x04;
private final static byte DESTINATION_SUB_ADDRESS = 0x05;
private final static byte BEARER_REPLY_OPTION = 0x06;
private final static byte CAUSE_CODES = 0x07;
private final static byte BEARER_DATA = 0x08;
/**
* Status of a previously submitted SMS.
* This field applies to SMS Delivery Acknowledge messages. 0 indicates success;
* Here, the error class is defined by the bits from 9-8, the status code by the bits from 7-0.
* See C.S0015-B, v2.0, 4.5.21 for a detailed description of possible values.
*/
private int status;
/** Specifies if a return of an acknowledgment is requested for send SMS */
private static final int RETURN_NO_ACK = 0;
private static final int RETURN_ACK = 1;
private SmsEnvelope mEnvelope;
private BearerData mBearerData;
/** @hide */
public SmsMessage(SmsAddress addr, SmsEnvelope env) {
mOriginatingAddress = addr;
mEnvelope = env;
createPdu();
}
public SmsMessage() {}
public static class SubmitPdu extends SubmitPduBase {
}
/**
* Create an SmsMessage from a raw PDU.
* Note: In CDMA the PDU is just a byte representation of the received Sms.
*/
public static SmsMessage createFromPdu(byte[] pdu) {
SmsMessage msg = new SmsMessage();
try {
msg.parsePdu(pdu);
return msg;
} catch (RuntimeException ex) {
Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
} catch (OutOfMemoryError e) {
Log.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
return null;
}
}
/**
* Create an SmsMessage from an SMS EF record.
*
* @param index Index of SMS record. This should be index in ArrayList
* returned by RuimSmsInterfaceManager.getAllMessagesFromIcc + 1.
* @param data Record data.
* @return An SmsMessage representing the record.
*
* @hide
*/
public static SmsMessage createFromEfRecord(int index, byte[] data) {
try {
SmsMessage msg = new SmsMessage();
msg.mIndexOnIcc = index;
// First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
// or STORED_UNSENT
// See 3GPP2 C.S0023 3.4.27
if ((data[0] & 1) == 0) {
Rlog.w(LOG_TAG, "SMS parsing failed: Trying to parse a free record");
return null;
} else {
msg.mStatusOnIcc = data[0] & 0x07;
}
// Second byte is the MSG_LEN, length of the message
// See 3GPP2 C.S0023 3.4.27
int size = data[1];
// Note: Data may include trailing FF's. That's OK; message
// should still parse correctly.
byte[] pdu = new byte[size];
System.arraycopy(data, 2, pdu, 0, size);
// the message has to be parsed before it can be displayed
// see gsm.SmsMessage
msg.parsePduFromEfRecord(pdu);
return msg;
} catch (RuntimeException ex) {
Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
}
}
/**
* Note: This function is a GSM specific functionality which is not supported in CDMA mode.
*/
public static int getTPLayerLengthForPDU(String pdu) {
Rlog.w(LOG_TAG, "getTPLayerLengthForPDU: is not supported in CDMA mode.");
return 0;
}
/**
* TODO(cleanup): why do getSubmitPdu methods take an scAddr input
* and do nothing with it? GSM allows us to specify a SC (eg,
* when responding to an SMS that explicitly requests the response
* is sent to a specific SC), or pass null to use the default
* value. Is there no similar notion in CDMA? Or do we just not
* have it hooked up?
*/
/**
* Get an SMS-SUBMIT PDU for a destination address and a message
*
* @param scAddr Service Centre address. Null means use default.
* @param destAddr Address of the recipient.
* @param message String representation of the message payload.
* @param statusReportRequested Indicates whether a report is requested for this message.
* @param smsHeader Array containing the data for the User Data Header, preceded
* by the Element Identifiers.
* @return a <code>SubmitPdu</code> containing the encoded SC
* address, if applicable, and the encoded message.
* Returns null on encode error.
* @hide
*/
public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
boolean statusReportRequested, SmsHeader smsHeader) {
/**
* TODO(cleanup): Do we really want silent failure like this?
* Would it not be much more reasonable to make sure we don't
* call this function if we really want nothing done?
*/
if (message == null || destAddr == null) {
return null;
}
UserData uData = new UserData();
uData.payloadStr = message;
uData.userDataHeader = smsHeader;
return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
}
/**
* Get an SMS-SUBMIT PDU for a data message to a destination address and port.
*
* @param scAddr Service Centre address. null == use default
* @param destAddr the address of the destination for the message
* @param destPort the port to deliver the message to at the
* destination
* @param data the data for the message
* @return a <code>SubmitPdu</code> containing the encoded SC
* address, if applicable, and the encoded message.
* Returns null on encode error.
*/
public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, int destPort,
byte[] data, boolean statusReportRequested) {
/**
* TODO(cleanup): this is not a general-purpose SMS creation
* method, but rather something specialized to messages
* containing OCTET encoded (meaning non-human-readable) user
* data. The name should reflect that, and not just overload.
*/
SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
portAddrs.destPort = destPort;
portAddrs.origPort = 0;
portAddrs.areEightBits = false;
SmsHeader smsHeader = new SmsHeader();
smsHeader.portAddrs = portAddrs;
UserData uData = new UserData();
uData.userDataHeader = smsHeader;
uData.msgEncoding = UserData.ENCODING_OCTET;
uData.msgEncodingSet = true;
uData.payload = data;
return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
}
/**
* Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
*
* @param destAddr the address of the destination for the message
* @param userData the data for the message
* @param statusReportRequested Indicates whether a report is requested for this message.
* @return a <code>SubmitPdu</code> containing the encoded SC
* address, if applicable, and the encoded message.
* Returns null on encode error.
*/
public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
boolean statusReportRequested) {
return privateGetSubmitPdu(destAddr, statusReportRequested, userData);
}
/**
* Note: This function is a GSM specific functionality which is not supported in CDMA mode.
*/
@Override
public int getProtocolIdentifier() {
Rlog.w(LOG_TAG, "getProtocolIdentifier: is not supported in CDMA mode.");
// (3GPP TS 23.040): "no interworking, but SME to SME protocol":
return 0;
}
/**
* Note: This function is a GSM specific functionality which is not supported in CDMA mode.
*/
@Override
public boolean isReplace() {
Rlog.w(LOG_TAG, "isReplace: is not supported in CDMA mode.");
return false;
}
/**
* {@inheritDoc}
* Note: This function is a GSM specific functionality which is not supported in CDMA mode.
*/
@Override
public boolean isCphsMwiMessage() {
Rlog.w(LOG_TAG, "isCphsMwiMessage: is not supported in CDMA mode.");
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isMWIClearMessage() {
return ((mBearerData != null) && (mBearerData.numberOfMessages == 0));
}
/**
* {@inheritDoc}
*/
@Override
public boolean isMWISetMessage() {
return ((mBearerData != null) && (mBearerData.numberOfMessages > 0));
}
/**
* {@inheritDoc}
*/
@Override
public boolean isMwiDontStore() {
return ((mBearerData != null) &&
(mBearerData.numberOfMessages > 0) &&
(mBearerData.userData == null));
}
/**
* Returns the status for a previously submitted message.
* For not interfering with status codes from GSM, this status code is
* shifted to the bits 31-16.
*/
@Override
public int getStatus() {
return (status << 16);
}
/** Return true iff the bearer data message type is DELIVERY_ACK. */
@Override
public boolean isStatusReportMessage() {
return (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK);
}
/**
* Note: This function is a GSM specific functionality which is not supported in CDMA mode.
*/
@Override
public boolean isReplyPathPresent() {
Rlog.w(LOG_TAG, "isReplyPathPresent: is not supported in CDMA mode.");
return false;
}
/**
* Calculate the number of septets needed to encode the message.
*
* @param messageBody the message to encode
* @param use7bitOnly ignore (but still count) illegal characters if true
* @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
* @return TextEncodingDetails
*/
public static TextEncodingDetails calculateLength(CharSequence messageBody,
boolean use7bitOnly, boolean isEntireMsg) {
CharSequence newMsgBody = null;
Resources r = Resources.getSystem();
if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
newMsgBody = Sms7BitEncodingTranslator.translate(messageBody);
}
if (TextUtils.isEmpty(newMsgBody)) {
newMsgBody = messageBody;
}
return BearerData.calcTextEncodingDetails(newMsgBody, use7bitOnly, isEntireMsg);
}
/**
* Returns the teleservice type of the message.
* @return the teleservice:
* {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET},
* {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WMT},
* {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WEMT},
* {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_VMN},
* {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WAP}
*/
public int getTeleService() {
return mEnvelope.teleService;
}
/**
* Returns the message type of the message.
* @return the message type:
* {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_POINT_TO_POINT},
* {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_BROADCAST},
* {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_ACKNOWLEDGE},
*/
public int getMessageType() {
// NOTE: mEnvelope.messageType is not set correctly for cell broadcasts with some RILs.
// Use the service category parameter to detect CMAS and other cell broadcast messages.
if (mEnvelope.serviceCategory != 0) {
return SmsEnvelope.MESSAGE_TYPE_BROADCAST;
} else {
return SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
}
}
/**
* Decodes pdu to an empty SMS object.
* In the CDMA case the pdu is just an internal byte stream representation
* of the SMS Java-object.
* @see #createPdu()
*/
private void parsePdu(byte[] pdu) {
ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
DataInputStream dis = new DataInputStream(bais);
int length;
int bearerDataLength;
SmsEnvelope env = new SmsEnvelope();
CdmaSmsAddress addr = new CdmaSmsAddress();
try {
env.messageType = dis.readInt();
env.teleService = dis.readInt();
env.serviceCategory = dis.readInt();
addr.digitMode = dis.readByte();
addr.numberMode = dis.readByte();
addr.ton = dis.readByte();
addr.numberPlan = dis.readByte();
length = dis.readUnsignedByte();
addr.numberOfDigits = length;
// sanity check on the length
if (length > pdu.length) {
throw new RuntimeException(
"createFromPdu: Invalid pdu, addr.numberOfDigits " + length
+ " > pdu len " + pdu.length);
}
addr.origBytes = new byte[length];
dis.read(addr.origBytes, 0, length); // digits
env.bearerReply = dis.readInt();
// CauseCode values:
env.replySeqNo = dis.readByte();
env.errorClass = dis.readByte();
env.causeCode = dis.readByte();
//encoded BearerData:
bearerDataLength = dis.readInt();
// sanity check on the length
if (bearerDataLength > pdu.length) {
throw new RuntimeException(
"createFromPdu: Invalid pdu, bearerDataLength " + bearerDataLength
+ " > pdu len " + pdu.length);
}
env.bearerData = new byte[bearerDataLength];
dis.read(env.bearerData, 0, bearerDataLength);
dis.close();
} catch (IOException ex) {
throw new RuntimeException(
"createFromPdu: conversion from byte array to object failed: " + ex, ex);
} catch (Exception ex) {
Rlog.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex);
}
// link the filled objects to this SMS
mOriginatingAddress = addr;
env.origAddress = addr;
mEnvelope = env;
mPdu = pdu;
parseSms();
}
/**
* Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0
*/
private void parsePduFromEfRecord(byte[] pdu) {
ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
DataInputStream dis = new DataInputStream(bais);
SmsEnvelope env = new SmsEnvelope();
CdmaSmsAddress addr = new CdmaSmsAddress();
CdmaSmsSubaddress subAddr = new CdmaSmsSubaddress();
try {
env.messageType = dis.readByte();
while (dis.available() > 0) {
int parameterId = dis.readByte();
int parameterLen = dis.readUnsignedByte();
byte[] parameterData = new byte[parameterLen];
switch (parameterId) {
case TELESERVICE_IDENTIFIER:
/*
* 16 bit parameter that identifies which upper layer
* service access point is sending or should receive
* this message
*/
env.teleService = dis.readUnsignedShort();
Rlog.i(LOG_TAG, "teleservice = " + env.teleService);
break;
case SERVICE_CATEGORY:
/*
* 16 bit parameter that identifies type of service as
* in 3GPP2 C.S0015-0 Table 3.4.3.2-1
*/
env.serviceCategory = dis.readUnsignedShort();
break;
case ORIGINATING_ADDRESS:
case DESTINATION_ADDRESS:
dis.read(parameterData, 0, parameterLen);
BitwiseInputStream addrBis = new BitwiseInputStream(parameterData);
addr.digitMode = addrBis.read(1);
addr.numberMode = addrBis.read(1);
int numberType = 0;
if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
numberType = addrBis.read(3);
addr.ton = numberType;
if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK)
addr.numberPlan = addrBis.read(4);
}
addr.numberOfDigits = addrBis.read(8);
byte[] data = new byte[addr.numberOfDigits];
byte b = 0x00;
if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
/* As per 3GPP2 C.S0005-0 Table 2.7.1.3.2.4-4 */
for (int index = 0; index < addr.numberOfDigits; index++) {
b = (byte) (0xF & addrBis.read(4));
// convert the value if it is 4-bit DTMF to 8
// bit
data[index] = convertDtmfToAscii(b);
}
} else if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) {
for (int index = 0; index < addr.numberOfDigits; index++) {
b = (byte) (0xFF & addrBis.read(8));
data[index] = b;
}
} else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) {
if (numberType == 2)
Rlog.e(LOG_TAG, "TODO: Originating Addr is email id");
else
Rlog.e(LOG_TAG,
"TODO: Originating Addr is data network address");
} else {
Rlog.e(LOG_TAG, "Originating Addr is of incorrect type");
}
} else {
Rlog.e(LOG_TAG, "Incorrect Digit mode");
}
addr.origBytes = data;
Rlog.i(LOG_TAG, "Originating Addr=" + addr.toString());
break;
case ORIGINATING_SUB_ADDRESS:
case DESTINATION_SUB_ADDRESS:
dis.read(parameterData, 0, parameterLen);
BitwiseInputStream subAddrBis = new BitwiseInputStream(parameterData);
subAddr.type = subAddrBis.read(3);
subAddr.odd = subAddrBis.readByteArray(1)[0];
int subAddrLen = subAddrBis.read(8);
byte[] subdata = new byte[subAddrLen];
for (int index = 0; index < subAddrLen; index++) {
b = (byte) (0xFF & subAddrBis.read(4));
// convert the value if it is 4-bit DTMF to 8 bit
subdata[index] = convertDtmfToAscii(b);
}
subAddr.origBytes = subdata;
break;
case BEARER_REPLY_OPTION:
dis.read(parameterData, 0, parameterLen);
BitwiseInputStream replyOptBis = new BitwiseInputStream(parameterData);
env.bearerReply = replyOptBis.read(6);
break;
case CAUSE_CODES:
dis.read(parameterData, 0, parameterLen);
BitwiseInputStream ccBis = new BitwiseInputStream(parameterData);
env.replySeqNo = ccBis.readByteArray(6)[0];
env.errorClass = ccBis.readByteArray(2)[0];
if (env.errorClass != 0x00)
env.causeCode = ccBis.readByteArray(8)[0];
break;
case BEARER_DATA:
dis.read(parameterData, 0, parameterLen);
env.bearerData = parameterData;
break;
default:
throw new Exception("unsupported parameterId (" + parameterId + ")");
}
}
bais.close();
dis.close();
} catch (Exception ex) {
Rlog.e(LOG_TAG, "parsePduFromEfRecord: conversion from pdu to SmsMessage failed" + ex);
}
// link the filled objects to this SMS
mOriginatingAddress = addr;
env.origAddress = addr;
env.origSubaddress = subAddr;
mEnvelope = env;
mPdu = pdu;
parseSms();
}
/**
* Parses a SMS message from its BearerData stream. (mobile-terminated only)
*/
public void parseSms() {
// Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6
// It contains only an 8-bit number with the number of messages waiting
if (mEnvelope.teleService == SmsEnvelope.TELESERVICE_MWI) {
mBearerData = new BearerData();
if (mEnvelope.bearerData != null) {
mBearerData.numberOfMessages = 0x000000FF & mEnvelope.bearerData[0];
}
if (VDBG) {
Rlog.d(LOG_TAG, "parseSms: get MWI " +
Integer.toString(mBearerData.numberOfMessages));
}
return;
}
mBearerData = BearerData.decode(mEnvelope.bearerData);
if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
Rlog.d(LOG_TAG, "MT raw BearerData = '" +
HexDump.toHexString(mEnvelope.bearerData) + "'");
Rlog.d(LOG_TAG, "MT (decoded) BearerData = " + mBearerData);
}
mMessageRef = mBearerData.messageId;
if (mBearerData.userData != null) {
mUserData = mBearerData.userData.payload;
mUserDataHeader = mBearerData.userData.userDataHeader;
mMessageBody = mBearerData.userData.payloadStr;
}
if (mOriginatingAddress != null) {
mOriginatingAddress.address = new String(mOriginatingAddress.origBytes);
if (mOriginatingAddress.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) {
if (mOriginatingAddress.address.charAt(0) != '+') {
mOriginatingAddress.address = "+" + mOriginatingAddress.address;
}
}
if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
+ mOriginatingAddress.address);
}
if (mBearerData.msgCenterTimeStamp != null) {
mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true);
}
if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
// Message Type (See 3GPP2 C.S0015-B, v2, 4.5.1)
if (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) {
// The BearerData MsgStatus subparameter should only be
// included for DELIVERY_ACK messages. If it occurred for
// other messages, it would be unclear what the status
// being reported refers to. The MsgStatus subparameter
// is primarily useful to indicate error conditions -- a
// message without this subparameter is assumed to
// indicate successful delivery (status == 0).
if (! mBearerData.messageStatusSet) {
Rlog.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" +
(mUserData == null ? "also missing" : "does have") +
" userData).");
status = 0;
} else {
status = mBearerData.errorClass << 8;
status |= mBearerData.messageStatus;
}
} else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER) {
throw new RuntimeException("Unsupported message type: " + mBearerData.messageType);
}
if (mMessageBody != null) {
if (VDBG) Rlog.v(LOG_TAG, "SMS message body: '" + mMessageBody + "'");
parseMessageBody();
} else if ((mUserData != null) && VDBG) {
Rlog.v(LOG_TAG, "SMS payload: '" + IccUtils.bytesToHexString(mUserData) + "'");
}
}
/**
* Parses a broadcast SMS, possibly containing a CMAS alert.
*/
public SmsCbMessage parseBroadcastSms() {
BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
if (bData == null) {
Rlog.w(LOG_TAG, "BearerData.decode() returned null");
return null;
}
if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
}
String plmn = TelephonyManager.getDefault().getNetworkOperator();
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}
*/
@Override
public SmsConstants.MessageClass getMessageClass() {
if (BearerData.DISPLAY_MODE_IMMEDIATE == mBearerData.displayMode ) {
return SmsConstants.MessageClass.CLASS_0;
} else {
return SmsConstants.MessageClass.UNKNOWN;
}
}
/**
* Calculate the next message id, starting at 1 and iteratively
* incrementing within the range 1..65535 remembering the state
* via a persistent system property. (See C.S0015-B, v2.0,
* 4.3.1.5) Since this routine is expected to be accessed via via
* binder-call, and hence should be thread-safe, it has been
* synchronized.
*/
public synchronized static int getNextMessageId() {
// Testing and dialog with partners has indicated that
// msgId==0 is (sometimes?) treated specially by lower levels.
// Specifically, the ID is not preserved for delivery ACKs.
// Hence, avoid 0 -- constraining the range to 1..65535.
int msgId = SystemProperties.getInt(TelephonyProperties.PROPERTY_CDMA_MSG_ID, 1);
String nextMsgId = Integer.toString((msgId % 0xFFFF) + 1);
try{
SystemProperties.set(TelephonyProperties.PROPERTY_CDMA_MSG_ID, nextMsgId);
if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
Rlog.d(LOG_TAG, "next " + TelephonyProperties.PROPERTY_CDMA_MSG_ID + " = " + nextMsgId);
Rlog.d(LOG_TAG, "readback gets " +
SystemProperties.get(TelephonyProperties.PROPERTY_CDMA_MSG_ID));
}
} catch(RuntimeException ex) {
Rlog.e(LOG_TAG, "set nextMessage ID failed: " + ex);
}
return msgId;
}
/**
* Creates BearerData and Envelope from parameters for a Submit SMS.
* @return byte stream for SubmitPdu.
*/
private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
UserData userData) {
/**
* TODO(cleanup): give this function a more meaningful name.
*/
/**
* TODO(cleanup): Make returning null from the getSubmitPdu
* variations meaningful -- clean up the error feedback
* mechanism, and avoid null pointer exceptions.
*/
/**
* North America Plus Code :
* Convert + code to 011 and dial out for international SMS
*/
CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(destAddrStr));
if (destAddr == null) return null;
BearerData bearerData = new BearerData();
bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
bearerData.messageId = getNextMessageId();
bearerData.deliveryAckReq = statusReportRequested;
bearerData.userAckReq = false;
bearerData.readAckReq = false;
bearerData.reportReq = false;
bearerData.userData = userData;
byte[] encodedBearerData = BearerData.encode(bearerData);
if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData);
Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'");
}
if (encodedBearerData == null) return null;
int teleservice = bearerData.hasUserDataHeader ?
SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;
SmsEnvelope envelope = new SmsEnvelope();
envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
envelope.teleService = teleservice;
envelope.destAddress = destAddr;
envelope.bearerReply = RETURN_ACK;
envelope.bearerData = encodedBearerData;
/**
* TODO(cleanup): envelope looks to be a pointless class, get
* rid of it. Also -- most of the envelope fields set here
* are ignored, why?
*/
try {
/**
* TODO(cleanup): reference a spec and get rid of the ugly comments
*/
ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(envelope.teleService);
dos.writeInt(0); //servicePresent
dos.writeInt(0); //serviceCategory
dos.write(destAddr.digitMode);
dos.write(destAddr.numberMode);
dos.write(destAddr.ton); // number_type
dos.write(destAddr.numberPlan);
dos.write(destAddr.numberOfDigits);
dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
// Subaddress is not supported.
dos.write(0); //subaddressType
dos.write(0); //subaddr_odd
dos.write(0); //subaddr_nbr_of_digits
dos.write(encodedBearerData.length);
dos.write(encodedBearerData, 0, encodedBearerData.length);
dos.close();
SubmitPdu pdu = new SubmitPdu();
pdu.encodedMessage = baos.toByteArray();
pdu.encodedScAddress = null;
return pdu;
} catch(IOException ex) {
Rlog.e(LOG_TAG, "creating SubmitPdu failed: " + ex);
}
return null;
}
/**
* Creates byte array (pseudo pdu) from SMS object.
* Note: Do not call this method more than once per object!
* @hide
*/
public void createPdu() {
SmsEnvelope env = mEnvelope;
CdmaSmsAddress addr = env.origAddress;
ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
try {
dos.writeInt(env.messageType);
dos.writeInt(env.teleService);
dos.writeInt(env.serviceCategory);
dos.writeByte(addr.digitMode);
dos.writeByte(addr.numberMode);
dos.writeByte(addr.ton);
dos.writeByte(addr.numberPlan);
dos.writeByte(addr.numberOfDigits);
dos.write(addr.origBytes, 0, addr.origBytes.length); // digits
dos.writeInt(env.bearerReply);
// CauseCode values:
dos.writeByte(env.replySeqNo);
dos.writeByte(env.errorClass);
dos.writeByte(env.causeCode);
//encoded BearerData:
dos.writeInt(env.bearerData.length);
dos.write(env.bearerData, 0, env.bearerData.length);
dos.close();
/**
* TODO(cleanup) -- The mPdu field is managed in
* a fragile manner, and it would be much nicer if
* accessing the serialized representation used a less
* fragile mechanism. Maybe the getPdu method could
* generate a representation if there was not yet one?
*/
mPdu = baos.toByteArray();
} catch (IOException ex) {
Rlog.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex);
}
}
/**
* Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character
* @hide
*/
public static byte convertDtmfToAscii(byte dtmfDigit) {
byte asciiDigit;
switch (dtmfDigit) {
case 0: asciiDigit = 68; break; // 'D'
case 1: asciiDigit = 49; break; // '1'
case 2: asciiDigit = 50; break; // '2'
case 3: asciiDigit = 51; break; // '3'
case 4: asciiDigit = 52; break; // '4'
case 5: asciiDigit = 53; break; // '5'
case 6: asciiDigit = 54; break; // '6'
case 7: asciiDigit = 55; break; // '7'
case 8: asciiDigit = 56; break; // '8'
case 9: asciiDigit = 57; break; // '9'
case 10: asciiDigit = 48; break; // '0'
case 11: asciiDigit = 42; break; // '*'
case 12: asciiDigit = 35; break; // '#'
case 13: asciiDigit = 65; break; // 'A'
case 14: asciiDigit = 66; break; // 'B'
case 15: asciiDigit = 67; break; // 'C'
default:
asciiDigit = 32; // Invalid DTMF code
break;
}
return asciiDigit;
}
/** This function shall be called to get the number of voicemails.
* @hide
*/
public int getNumOfVoicemails() {
return mBearerData.numberOfMessages;
}
/**
* Returns a byte array that can be use to uniquely identify a received SMS message.
* C.S0015-B 4.3.1.6 Unique Message Identification.
*
* @return byte array uniquely identifying the message.
* @hide
*/
public byte[] getIncomingSmsFingerprint() {
ByteArrayOutputStream output = new ByteArrayOutputStream();
output.write(mEnvelope.serviceCategory);
output.write(mEnvelope.teleService);
output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length);
output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length);
output.write(mEnvelope.origSubaddress.origBytes, 0,
mEnvelope.origSubaddress.origBytes.length);
return output.toByteArray();
}
/**
* Returns the list of service category program data, if present.
* @return a list of CdmaSmsCbProgramData objects, or null if not present
* @hide
*/
public ArrayList<CdmaSmsCbProgramData> getSmsCbProgramData() {
return mBearerData.serviceCategoryProgramData;
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright (C) 2008 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.sms;
import android.util.SparseIntArray;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.util.HexDump;
public class UserData {
/**
* User data encoding types.
* (See 3GPP2 C.R1001-F, v1.0, table 9.1-1)
*/
public static final int ENCODING_OCTET = 0x00;
public static final int ENCODING_IS91_EXTENDED_PROTOCOL = 0x01;
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_LATIN = 0x08;
public static final int ENCODING_GSM_7BIT_ALPHABET = 0x09;
public static final int ENCODING_GSM_DCS = 0x0A;
/**
* IS-91 message types.
* (See TIA/EIS/IS-91-A-ENGL 1999, table 3.7.1.1-3)
*/
public static final int IS91_MSG_TYPE_VOICEMAIL_STATUS = 0x82;
public static final int IS91_MSG_TYPE_SHORT_MESSAGE_FULL = 0x83;
public static final int IS91_MSG_TYPE_CLI = 0x84;
public static final int IS91_MSG_TYPE_SHORT_MESSAGE = 0x85;
/**
* US ASCII character mapping table.
*
* This table contains only the printable ASCII characters, with a
* 0x20 offset, meaning that the ASCII SPACE character is at index
* 0, with the resulting code of 0x20.
*
* Note this mapping is also equivalent to that used by both the
* IA5 and the IS-91 encodings. For the former this is defined
* using CCITT Rec. T.50 Tables 1 and 3. For the latter IS 637 B,
* Table 4.3.1.4.1-1 -- and note the encoding uses only 6 bits,
* and hence only maps entries up to the '_' character.
*
*/
public static final char[] ASCII_MAP = {
' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'};
/**
* Character to use when forced to encode otherwise unencodable
* characters, meaning those not in the respective ASCII or GSM
* 7-bit encoding tables. Current choice is SPACE, which is 0x20
* in both the GSM-7bit and ASCII-7bit encodings.
*/
static final byte UNENCODABLE_7_BIT_CHAR = 0x20;
/**
* Only elements between these indices in the ASCII table are printable.
*/
public static final int PRINTABLE_ASCII_MIN_INDEX = 0x20;
public static final int ASCII_NL_INDEX = 0x0A;
public static final int ASCII_CR_INDEX = 0x0D;
public static final SparseIntArray charToAscii = new SparseIntArray();
static {
for (int i = 0; i < ASCII_MAP.length; i++) {
charToAscii.put(ASCII_MAP[i], PRINTABLE_ASCII_MIN_INDEX + i);
}
charToAscii.put('\n', ASCII_NL_INDEX);
charToAscii.put('\r', ASCII_CR_INDEX);
}
/*
* TODO(cleanup): Move this very generic functionality somewhere
* more general.
*/
/**
* Given a string generate a corresponding ASCII-encoded byte
* array, but limited to printable characters. If the input
* contains unprintable characters, return null.
*/
public static byte[] stringToAscii(String str) {
int len = str.length();
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
int charCode = charToAscii.get(str.charAt(i), -1);
if (charCode == -1) return null;
result[i] = (byte)charCode;
}
return result;
}
/**
* Mapping for ASCII values less than 32 are flow control signals
* and not used here.
*/
public static final int ASCII_MAP_BASE_INDEX = 0x20;
public static final int ASCII_MAP_MAX_INDEX = ASCII_MAP_BASE_INDEX + ASCII_MAP.length - 1;
/**
* Contains the data header of the user data
*/
public SmsHeader userDataHeader;
/**
* Contains the data encoding type for the SMS message
*/
public int msgEncoding;
public boolean msgEncodingSet = false;
public int msgType;
/**
* Number of invalid bits in the last byte of data.
*/
public int paddingBits;
public int numFields;
/**
* Contains the user data of a SMS message
* (See 3GPP2 C.S0015-B, v2, 4.5.2)
*/
public byte[] payload;
public String payloadStr;
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("UserData ");
builder.append("{ msgEncoding=" + (msgEncodingSet ? msgEncoding : "unset"));
builder.append(", msgType=" + msgType);
builder.append(", paddingBits=" + paddingBits);
builder.append(", numFields=" + numFields);
builder.append(", userDataHeader=" + userDataHeader);
builder.append(", payload='" + HexDump.toHexString(payload) + "'");
builder.append(", payloadStr='" + payloadStr + "'");
builder.append(" }");
return builder.toString();
}
}

View File

@@ -0,0 +1,6 @@
<HTML>
<BODY>
Provides CDMA-specific features for text/data/PDU SMS messages
@hide
</BODY>
</HTML>

View File

@@ -0,0 +1,153 @@
/*
* Copyright (C) 2006 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.PhoneNumberUtils;
import java.text.ParseException;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsAddress;
public class GsmSmsAddress extends SmsAddress {
static final int OFFSET_ADDRESS_LENGTH = 0;
static final int OFFSET_TOA = 1;
static final int OFFSET_ADDRESS_VALUE = 2;
/**
* New GsmSmsAddress from TS 23.040 9.1.2.5 Address Field
*
* @param offset the offset of the Address-Length byte
* @param length the length in bytes rounded up, e.g. "2 +
* (addressLength + 1) / 2"
* @throws ParseException
*/
public GsmSmsAddress(byte[] data, int offset, int length) throws ParseException {
origBytes = new byte[length];
System.arraycopy(data, offset, origBytes, 0, length);
// addressLength is the count of semi-octets, not bytes
int addressLength = origBytes[OFFSET_ADDRESS_LENGTH] & 0xff;
int toa = origBytes[OFFSET_TOA] & 0xff;
ton = 0x7 & (toa >> 4);
// TOA must have its high bit set
if ((toa & 0x80) != 0x80) {
throw new ParseException("Invalid TOA - high bit must be set. toa = " + toa,
offset + OFFSET_TOA);
}
if (isAlphanumeric()) {
// An alphanumeric address
int countSeptets = addressLength * 4 / 7;
address = GsmAlphabet.gsm7BitPackedToString(origBytes,
OFFSET_ADDRESS_VALUE, countSeptets);
} else {
// TS 23.040 9.1.2.5 says
// that "the MS shall interpret reserved values as 'Unknown'
// but shall store them exactly as received"
byte lastByte = origBytes[length - 1];
if ((addressLength & 1) == 1) {
// Make sure the final unused BCD digit is 0xf
origBytes[length - 1] |= 0xf0;
}
address = PhoneNumberUtils.calledPartyBCDToString(origBytes,
OFFSET_TOA, length - OFFSET_TOA);
// And restore origBytes
origBytes[length - 1] = lastByte;
}
}
@Override
public String getAddressString() {
return address;
}
/**
* Returns true if this is an alphanumeric address
*/
@Override
public boolean isAlphanumeric() {
return ton == TON_ALPHANUMERIC;
}
@Override
public boolean isNetworkSpecific() {
return ton == TON_NETWORK;
}
/**
* Returns true of this is a valid CPHS voice message waiting indicator
* address
*/
public boolean isCphsVoiceMessageIndicatorAddress() {
// CPHS-style MWI message
// See CPHS 4.7 B.4.2.1
//
// Basically:
//
// - Originating address should be 4 bytes long and alphanumeric
// - Decode will result with two chars:
// - Char 1
// 76543210
// ^ set/clear indicator (0 = clear)
// ^^^ type of indicator (000 = voice)
// ^^^^ must be equal to 0001
// - Char 2:
// 76543210
// ^ line number (0 = line 1)
// ^^^^^^^ set to 0
//
// Remember, since the alpha address is stored in 7-bit compact form,
// the "line number" is really the top bit of the first address value
// byte
return (origBytes[OFFSET_ADDRESS_LENGTH] & 0xff) == 4
&& isAlphanumeric() && (origBytes[OFFSET_TOA] & 0x0f) == 0;
}
/**
* Returns true if this is a valid CPHS voice message waiting indicator
* address indicating a "set" of "indicator 1" of type "voice message
* waiting"
*/
public boolean isCphsVoiceMessageSet() {
// 0x11 means "set" "voice message waiting" "indicator 1"
return isCphsVoiceMessageIndicatorAddress()
&& (origBytes[OFFSET_ADDRESS_VALUE] & 0xff) == 0x11;
}
/**
* Returns true if this is a valid CPHS voice message waiting indicator
* address indicating a "clear" of "indicator 1" of type "voice message
* waiting"
*/
public boolean isCphsVoiceMessageClear() {
// 0x10 means "clear" "voice message waiting" "indicator 1"
return isCphsVoiceMessageIndicatorAddress()
&& (origBytes[OFFSET_ADDRESS_VALUE] & 0xff) == 0x10;
}
}

View File

@@ -0,0 +1,314 @@
/*
* 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 static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
import android.content.Context;
import android.content.res.Resources;
import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
import android.util.Pair;
import com.android.internal.R;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsConstants;
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() { }
/**
* Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
* so we have to show the pre-built messages to the user.
*
* @param context Device context
* @param category ETWS message category defined in SmsCbConstants
* @return ETWS text message in string. Return an empty string if no match.
*/
private static String getEtwsPrimaryMessage(Context context, int category) {
final Resources r = context.getResources();
switch (category) {
case ETWS_WARNING_TYPE_EARTHQUAKE:
return r.getString(R.string.etws_primary_default_message_earthquake);
case ETWS_WARNING_TYPE_TSUNAMI:
return r.getString(R.string.etws_primary_default_message_tsunami);
case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
case ETWS_WARNING_TYPE_TEST_MESSAGE:
return r.getString(R.string.etws_primary_default_message_test);
case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
return r.getString(R.string.etws_primary_default_message_others);
default:
return "";
}
}
/**
* Create a new SmsCbMessage object from a header object plus one or more received PDUs.
*
* @param pdus PDU bytes
*/
public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
SmsCbLocation location, byte[][] pdus)
throws IllegalArgumentException {
if (header.isEtwsPrimaryNotification()) {
// ETSI TS 23.041 ETWS Primary Notification message
// ETWS primary message only contains 4 fields including serial number,
// message identifier, warning type, and warning security information.
// There is no field for the content/text so we get the text from the resources.
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
header.getSerialNumber(), location, header.getServiceCategory(), null,
getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
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());
}
}
/**
* 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 = SmsConstants.ENCODING_7BIT;
language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
break;
case 0x01:
hasLanguageIndicator = true;
if ((dataCodingScheme & 0x0f) == 0x01) {
encoding = SmsConstants.ENCODING_16BIT;
} else {
encoding = SmsConstants.ENCODING_7BIT;
}
break;
case 0x02:
encoding = SmsConstants.ENCODING_7BIT;
language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
break;
case 0x03:
encoding = SmsConstants.ENCODING_7BIT;
break;
case 0x04:
case 0x05:
switch ((dataCodingScheme & 0x0c) >> 2) {
case 0x01:
encoding = SmsConstants.ENCODING_8BIT;
break;
case 0x02:
encoding = SmsConstants.ENCODING_16BIT;
break;
case 0x00:
default:
encoding = SmsConstants.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 = SmsConstants.ENCODING_8BIT;
} else {
encoding = SmsConstants.ENCODING_7BIT;
}
break;
default:
// Reserved values are to be treated as 7-bit
encoding = SmsConstants.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 SmsConstants.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 SmsConstants.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);
}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright (C) 2009 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;
/**
* SmsBroadcastConfigInfo defines one configuration of Cell Broadcast
* Message (CBM) to be received by the ME
*
* fromServiceId - toServiceId defines a range of CBM message identifiers
* whose value is 0x0000 - 0xFFFF as defined in TS 23.041 9.4.1.2.2 for GMS
* and 9.4.4.2.2 for UMTS. All other values can be treated as empty
* CBM message ID.
*
* fromCodeScheme - toCodeScheme defines a range of CBM data coding schemes
* whose value is 0x00 - 0xFF as defined in TS 23.041 9.4.1.2.3 for GMS
* and 9.4.4.2.3 for UMTS.
* All other values can be treated as empty CBM data coding scheme.
*
* selected false means message types specified in {@code <fromServiceId, toServiceId>}
* and {@code <fromCodeScheme, toCodeScheme>} are not accepted, while true means accepted.
*
*/
public final class SmsBroadcastConfigInfo {
private int mFromServiceId;
private int mToServiceId;
private int mFromCodeScheme;
private int mToCodeScheme;
private boolean mSelected;
/**
* Initialize the object from rssi and cid.
*/
public SmsBroadcastConfigInfo(int fromId, int toId, int fromScheme,
int toScheme, boolean selected) {
mFromServiceId = fromId;
mToServiceId = toId;
mFromCodeScheme = fromScheme;
mToCodeScheme = toScheme;
mSelected = selected;
}
/**
* @param fromServiceId the fromServiceId to set
*/
public void setFromServiceId(int fromServiceId) {
mFromServiceId = fromServiceId;
}
/**
* @return the fromServiceId
*/
public int getFromServiceId() {
return mFromServiceId;
}
/**
* @param toServiceId the toServiceId to set
*/
public void setToServiceId(int toServiceId) {
mToServiceId = toServiceId;
}
/**
* @return the toServiceId
*/
public int getToServiceId() {
return mToServiceId;
}
/**
* @param fromCodeScheme the fromCodeScheme to set
*/
public void setFromCodeScheme(int fromCodeScheme) {
mFromCodeScheme = fromCodeScheme;
}
/**
* @return the fromCodeScheme
*/
public int getFromCodeScheme() {
return mFromCodeScheme;
}
/**
* @param toCodeScheme the toCodeScheme to set
*/
public void setToCodeScheme(int toCodeScheme) {
mToCodeScheme = toCodeScheme;
}
/**
* @return the toCodeScheme
*/
public int getToCodeScheme() {
return mToCodeScheme;
}
/**
* @param selected the selected to set
*/
public void setSelected(boolean selected) {
mSelected = selected;
}
/**
* @return the selected
*/
public boolean isSelected() {
return mSelected;
}
@Override
public String toString() {
return "SmsBroadcastConfigInfo: Id [" +
mFromServiceId + ',' + mToServiceId + "] Code [" +
mFromCodeScheme + ',' + mToCodeScheme + "] " +
(mSelected ? "ENABLED" : "DISABLED");
}
}

View File

@@ -0,0 +1,217 @@
/*
* 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;
/**
* 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 class SmsCbConstants {
/** Private constructor for utility class. */
private SmsCbConstants() { }
/** Channel 50 required by Brazil. ID 0~999 is allocated by GSMA */
public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_50
= 0x0032;
/** Channel 911 required by Taiwan NCC. ID 0~999 is allocated by GSMA */
public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_911
= 0x038F; // 911
/** Channel 919 required by Taiwan NCC and Israel. ID 0~999 is allocated by GSMA */
public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_919
= 0x0397; // 919
/** Channel 928 required by Israel. ID 0~999 is allocated by GSMA */
public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_928
= 0x03A0; // 928
/** Start of PWS Message Identifier range (includes ETWS and CMAS). */
public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER
= 0x1100; // 4352
/** Bitmask for messages of ETWS type (including future extensions). */
public static final int MESSAGE_ID_ETWS_TYPE_MASK
= 0xFFF8;
/** Value for messages of ETWS type after applying {@link #MESSAGE_ID_ETWS_TYPE_MASK}. */
public static final int MESSAGE_ID_ETWS_TYPE
= 0x1100; // 4352
/** ETWS Message Identifier for earthquake warning message. */
public static final int MESSAGE_ID_ETWS_EARTHQUAKE_WARNING
= 0x1100; // 4352
/** ETWS Message Identifier for tsunami warning message. */
public static final int MESSAGE_ID_ETWS_TSUNAMI_WARNING
= 0x1101; // 4353
/** ETWS Message Identifier for earthquake and tsunami combined warning message. */
public static final int MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING
= 0x1102; // 4354
/** ETWS Message Identifier for test message. */
public static final int MESSAGE_ID_ETWS_TEST_MESSAGE
= 0x1103; // 4355
/** ETWS Message Identifier for messages related to other emergency types. */
public static final int MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE
= 0x1104; // 4356
/** Start of CMAS Message Identifier range. */
public static final int MESSAGE_ID_CMAS_FIRST_IDENTIFIER
= 0x1112; // 4370
/** CMAS Message Identifier for Presidential Level alerts. */
public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL
= 0x1112; // 4370
/** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED
= 0x1113; // 4371
/** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY
= 0x1114; // 4372
/** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED
= 0x1115; // 4373
/** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY
= 0x1116; // 4374
/** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed. */
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED
= 0x1117; // 4375
/** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely. */
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY
= 0x1118; // 4376
/** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed. */
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED
= 0x1119; // 4377
/** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely. */
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY
= 0x111A; // 4378
/** CMAS Message Identifier for Child Abduction Emergency (Amber Alert). */
public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY
= 0x111B; // 4379
/** CMAS Message Identifier for the Required Monthly Test. */
public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST
= 0x111C; // 4380
/** CMAS Message Identifier for CMAS Exercise. */
public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE
= 0x111D; // 4381
/** CMAS Message Identifier for operator defined use. */
public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE
= 0x111E; // 4382
/** CMAS Message Identifier for Presidential Level alerts for additional languages
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE
= 0x111F; // 4383
/** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE
= 0x1120; // 4384
/** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE
= 0x1121; // 4385
/** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE
= 0x1122; // 4386
/** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE
= 0x1123; // 4387
/** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE
= 0x1124; // 4388
/** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely
* for additional languages.*/
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE
= 0x1125; // 4389
/** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE
= 0x1126; // 4390
/** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely
* for additional languages.*/
public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE
= 0x1127; // 4391
/** CMAS Message Identifier for Child Abduction Emergency (Amber Alert)
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE
= 0x1128; // 4392
/** CMAS Message Identifier for the Required Monthly Test
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE
= 0x1129; // 4393
/** CMAS Message Identifier for CMAS Exercise
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE
= 0x112A; // 4394
/** CMAS Message Identifier for operator defined use
* for additional languages. */
public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE
= 0x112B; // 4395
/** End of CMAS Message Identifier range (including future extensions). */
public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER
= 0x112F; // 4399
/** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER
= 0x18FF; // 6399
/** ETWS serial number flag to activate the popup display. */
public static final int SERIAL_NUMBER_ETWS_ACTIVATE_POPUP
= 0x1000; // 4096
/** ETWS serial number flag to activate the emergency user alert. */
public static final int SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT
= 0x2000; // 8192
}

View File

@@ -0,0 +1,450 @@
/*
* Copyright (C) 2010 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.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.
*/
public class SmsCbHeader {
/**
* Length of SMS-CB header
*/
static final int PDU_HEADER_LENGTH = 6;
/**
* GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
*/
static final int FORMAT_GSM = 1;
/**
* UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2
*/
static final int FORMAT_UMTS = 2;
/**
* GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
*/
static final int FORMAT_ETWS_PRIMARY = 3;
/**
* Message type value as defined in 3gpp TS 25.324, section 11.1.
*/
private static final int MESSAGE_TYPE_CBS_MESSAGE = 1;
/**
* Length of GSM pdus
*/
private static final int PDU_LENGTH_GSM = 88;
/**
* Maximum length of ETWS primary message GSM pdus
*/
private static final int PDU_LENGTH_ETWS = 56;
private final int mGeographicalScope;
/** The serial number combines geographical scope, message code, and update number. */
private final int mSerialNumber;
/** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */
private final int mMessageIdentifier;
private final int mDataCodingScheme;
private final int mPageIndex;
private final int mNrOfPages;
private final int mFormat;
/** ETWS warning notification info. */
private final SmsCbEtwsInfo mEtwsInfo;
/** CMAS warning notification info. */
private final SmsCbCmasInfo mCmasInfo;
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) {
// can be ETWS or GSM format.
// Per TS23.041 9.4.1.2 and 9.4.1.3.2, GSM and ETWS format both
// contain serial number which contains GS, Message Code, and Update Number
// per 9.4.1.2.1, and message identifier in same octets
mGeographicalScope = (pdu[0] & 0xc0) >>> 6;
mSerialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
mMessageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
if (isEtwsMessage() && pdu.length <= PDU_LENGTH_ETWS) {
mFormat = FORMAT_ETWS_PRIMARY;
mDataCodingScheme = -1;
mPageIndex = -1;
mNrOfPages = -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,
true, warningSecurityInfo);
mCmasInfo = null;
return; // skip the ETWS/CMAS initialization code for regular notifications
} else {
// GSM pdus are no more than 88 bytes
mFormat = FORMAT_GSM;
mDataCodingScheme = pdu[4] & 0xff;
// Check for invalid page parameter
int pageIndex = (pdu[5] & 0xf0) >>> 4;
int nrOfPages = pdu[5] & 0x0f;
if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) {
pageIndex = 1;
nrOfPages = 1;
}
mPageIndex = pageIndex;
mNrOfPages = nrOfPages;
}
} 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
mFormat = FORMAT_UMTS;
int messageType = pdu[0];
if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
throw new IllegalArgumentException("Unsupported message type " + messageType);
}
mMessageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
mGeographicalScope = (pdu[3] & 0xc0) >>> 6;
mSerialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
mDataCodingScheme = pdu[5] & 0xff;
// We will always consider a UMTS message as having one single page
// since there's only one instance of the header, even though the
// actual payload may contain several pages.
mPageIndex = 1;
mNrOfPages = 1;
}
if (isEtwsMessage()) {
boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
boolean activatePopup = isEtwsPopupAlert();
int warningType = getEtwsWarningType();
mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
false, 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 mGeographicalScope;
}
int getSerialNumber() {
return mSerialNumber;
}
int getServiceCategory() {
return mMessageIdentifier;
}
int getDataCodingScheme() {
return mDataCodingScheme;
}
int getPageIndex() {
return mPageIndex;
}
int getNumberOfPages() {
return mNrOfPages;
}
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 mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
&& mMessageIdentifier <= 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 (mMessageIdentifier & 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 mFormat == 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 mFormat == 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 mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
&& mMessageIdentifier <= 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 (mSerialNumber & 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 (mSerialNumber & 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 mMessageIdentifier - 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 (mMessageIdentifier) {
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE:
return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE:
return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE:
return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE:
return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE:
return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
default:
return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
}
}
/**
* Returns the severity for a CMAS warning notification. This is only available for extreme
* and severe alerts, not for other types such as Presidential Level and AMBER alerts.
* This method assumes that the message ID has already been checked for CMAS type.
* @return the CMAS severity as defined in {@link SmsCbCmasInfo}
*/
private int getCmasSeverity() {
switch (mMessageIdentifier) {
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
default:
return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
}
}
/**
* Returns the urgency for a CMAS warning notification. This is only available for extreme
* and severe alerts, not for other types such as Presidential Level and AMBER alerts.
* This method assumes that the message ID has already been checked for CMAS type.
* @return the CMAS urgency as defined in {@link SmsCbCmasInfo}
*/
private int getCmasUrgency() {
switch (mMessageIdentifier) {
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
default:
return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
}
}
/**
* Returns the certainty for a CMAS warning notification. This is only available for extreme
* and severe alerts, not for other types such as Presidential Level and AMBER alerts.
* This method assumes that the message ID has already been checked for CMAS type.
* @return the CMAS certainty as defined in {@link SmsCbCmasInfo}
*/
private int getCmasCertainty() {
switch (mMessageIdentifier) {
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
default:
return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
}
}
@Override
public String toString() {
return "SmsCbHeader{GS=" + mGeographicalScope + ", serialNumber=0x" +
Integer.toHexString(mSerialNumber) +
", messageIdentifier=0x" + Integer.toHexString(mMessageIdentifier) +
", DCS=0x" + Integer.toHexString(mDataCodingScheme) +
", page " + mPageIndex + " of " + mNrOfPages + '}';
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,570 @@
/*
* Copyright (C) 2006 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.uicc;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.telephony.Rlog;
import com.android.internal.telephony.GsmAlphabet;
import java.io.UnsupportedEncodingException;
/**
* Various methods, useful for dealing with SIM data.
*/
public class IccUtils {
static final String LOG_TAG="IccUtils";
/**
* Many fields in GSM SIM's are stored as nibble-swizzled BCD
*
* Assumes left-justified field that may be padded right with 0xf
* values.
*
* Stops on invalid BCD value, returning string so far
*/
public static String
bcdToString(byte[] data, int offset, int length) {
StringBuilder ret = new StringBuilder(length*2);
for (int i = offset ; i < offset + length ; i++) {
int v;
v = data[i] & 0xf;
if (v > 9) break;
ret.append((char)('0' + v));
v = (data[i] >> 4) & 0xf;
// Some PLMNs have 'f' as high nibble, ignore it
if (v == 0xf) continue;
if (v > 9) break;
ret.append((char)('0' + v));
}
return ret.toString();
}
/**
* PLMN (MCC/MNC) is encoded as per 24.008 10.5.1.3
* Returns a concatenated string of MCC+MNC, stripping
* a trailing character for a 2-digit MNC
*/
public static String bcdPlmnToString(byte[] data, int offset) {
if (offset + 3 > data.length) {
return null;
}
byte[] trans = new byte[3];
trans[0] = (byte) ((data[0 + offset] << 4) | ((data[0 + offset] >> 4) & 0xF));
trans[1] = (byte) ((data[1 + offset] << 4) | (data[2 + offset] & 0xF));
trans[2] = (byte) ((data[2 + offset] & 0xF0) | ((data[1 + offset] >> 4) & 0xF));
String ret = bytesToHexString(trans);
// For a 2-digit MNC we trim the trailing 'f'
if (ret.endsWith("f")) {
ret = ret.substring(0, ret.length() - 1);
}
return ret;
}
/**
* Some fields (like ICC ID) in GSM SIMs are stored as nibble-swizzled BCH
*/
public static String
bchToString(byte[] data, int offset, int length) {
StringBuilder ret = new StringBuilder(length*2);
for (int i = offset ; i < offset + length ; i++) {
int v;
v = data[i] & 0xf;
ret.append("0123456789abcdef".charAt(v));
v = (data[i] >> 4) & 0xf;
ret.append("0123456789abcdef".charAt(v));
}
return ret.toString();
}
/**
* Decode cdma byte into String.
*/
public static String
cdmaBcdToString(byte[] data, int offset, int length) {
StringBuilder ret = new StringBuilder(length);
int count = 0;
for (int i = offset; count < length; i++) {
int v;
v = data[i] & 0xf;
if (v > 9) v = 0;
ret.append((char)('0' + v));
if (++count == length) break;
v = (data[i] >> 4) & 0xf;
if (v > 9) v = 0;
ret.append((char)('0' + v));
++count;
}
return ret.toString();
}
/**
* Decodes a GSM-style BCD byte, returning an int ranging from 0-99.
*
* In GSM land, the least significant BCD digit is stored in the most
* significant nibble.
*
* Out-of-range digits are treated as 0 for the sake of the time stamp,
* because of this:
*
* TS 23.040 section 9.2.3.11
* "if the MS receives a non-integer value in the SCTS, it shall
* assume the digit is set to 0 but shall store the entire field
* exactly as received"
*/
public static int
gsmBcdByteToInt(byte b) {
int ret = 0;
// treat out-of-range BCD values as 0
if ((b & 0xf0) <= 0x90) {
ret = (b >> 4) & 0xf;
}
if ((b & 0x0f) <= 0x09) {
ret += (b & 0xf) * 10;
}
return ret;
}
/**
* Decodes a CDMA style BCD byte like {@link #gsmBcdByteToInt}, but
* opposite nibble format. The least significant BCD digit
* is in the least significant nibble and the most significant
* is in the most significant nibble.
*/
public static int
cdmaBcdByteToInt(byte b) {
int ret = 0;
// treat out-of-range BCD values as 0
if ((b & 0xf0) <= 0x90) {
ret = ((b >> 4) & 0xf) * 10;
}
if ((b & 0x0f) <= 0x09) {
ret += (b & 0xf);
}
return ret;
}
/**
* Decodes a string field that's formatted like the EF[ADN] alpha
* identifier
*
* From TS 51.011 10.5.1:
* Coding:
* this alpha tagging shall use either
* - the SMS default 7 bit coded alphabet as defined in
* TS 23.038 [12] with bit 8 set to 0. The alpha identifier
* shall be left justified. Unused bytes shall be set to 'FF'; or
* - one of the UCS2 coded options as defined in annex B.
*
* Annex B from TS 11.11 V8.13.0:
* 1) If the first octet in the alpha string is '80', then the
* remaining octets are 16 bit UCS2 characters ...
* 2) if the first octet in the alpha string is '81', then the
* second octet contains a value indicating the number of
* characters in the string, and the third octet contains an
* 8 bit number which defines bits 15 to 8 of a 16 bit
* base pointer, where bit 16 is set to zero and bits 7 to 1
* are also set to zero. These sixteen bits constitute a
* base pointer to a "half page" in the UCS2 code space, to be
* used with some or all of the remaining octets in the string.
* The fourth and subsequent octets contain codings as follows:
* If bit 8 of the octet is set to zero, the remaining 7 bits
* of the octet contain a GSM Default Alphabet character,
* whereas if bit 8 of the octet is set to one, then the
* remaining seven bits are an offset value added to the
* 16 bit base pointer defined earlier...
* 3) If the first octet of the alpha string is set to '82', then
* the second octet contains a value indicating the number of
* characters in the string, and the third and fourth octets
* contain a 16 bit number which defines the complete 16 bit
* base pointer to a "half page" in the UCS2 code space...
*/
public static String
adnStringFieldToString(byte[] data, int offset, int length) {
if (length == 0) {
return "";
}
if (length >= 1) {
if (data[offset] == (byte) 0x80) {
int ucslen = (length - 1) / 2;
String ret = null;
try {
ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
} catch (UnsupportedEncodingException ex) {
Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException",
ex);
}
if (ret != null) {
// trim off trailing FFFF characters
ucslen = ret.length();
while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF')
ucslen--;
return ret.substring(0, ucslen);
}
}
}
boolean isucs2 = false;
char base = '\0';
int len = 0;
if (length >= 3 && data[offset] == (byte) 0x81) {
len = data[offset + 1] & 0xFF;
if (len > length - 3)
len = length - 3;
base = (char) ((data[offset + 2] & 0xFF) << 7);
offset += 3;
isucs2 = true;
} else if (length >= 4 && data[offset] == (byte) 0x82) {
len = data[offset + 1] & 0xFF;
if (len > length - 4)
len = length - 4;
base = (char) (((data[offset + 2] & 0xFF) << 8) |
(data[offset + 3] & 0xFF));
offset += 4;
isucs2 = true;
}
if (isucs2) {
StringBuilder ret = new StringBuilder();
while (len > 0) {
// UCS2 subset case
if (data[offset] < 0) {
ret.append((char) (base + (data[offset] & 0x7F)));
offset++;
len--;
}
// GSM character set case
int count = 0;
while (count < len && data[offset + count] >= 0)
count++;
ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
offset, count));
offset += count;
len -= count;
}
return ret.toString();
}
Resources resource = Resources.getSystem();
String defaultCharset = "";
try {
defaultCharset =
resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset);
} catch (NotFoundException e) {
// Ignore Exception and defaultCharset is set to a empty string.
}
return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
}
static int
hexCharToInt(char c) {
if (c >= '0' && c <= '9') return (c - '0');
if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
throw new RuntimeException ("invalid hex char '" + c + "'");
}
/**
* Converts a hex String to a byte array.
*
* @param s A string of hexadecimal characters, must be an even number of
* chars long
*
* @return byte array representation
*
* @throws RuntimeException on invalid format
*/
public static byte[]
hexStringToBytes(String s) {
byte[] ret;
if (s == null) return null;
int sz = s.length();
ret = new byte[sz/2];
for (int i=0 ; i <sz ; i+=2) {
ret[i/2] = (byte) ((hexCharToInt(s.charAt(i)) << 4)
| hexCharToInt(s.charAt(i+1)));
}
return ret;
}
/**
* Converts a byte array into a String of hexadecimal characters.
*
* @param bytes an array of bytes
*
* @return hex string representation of bytes array
*/
public static String
bytesToHexString(byte[] bytes) {
if (bytes == null) return null;
StringBuilder ret = new StringBuilder(2*bytes.length);
for (int i = 0 ; i < bytes.length ; i++) {
int b;
b = 0x0f & (bytes[i] >> 4);
ret.append("0123456789abcdef".charAt(b));
b = 0x0f & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
/**
* Convert a TS 24.008 Section 10.5.3.5a Network Name field to a string
* "offset" points to "octet 3", the coding scheme byte
* empty string returned on decode error
*/
public static String
networkNameToString(byte[] data, int offset, int length) {
String ret;
if ((data[offset] & 0x80) != 0x80 || length < 1) {
return "";
}
switch ((data[offset] >>> 4) & 0x7) {
case 0:
// SMS character set
int countSeptets;
int unusedBits = data[offset] & 7;
countSeptets = (((length - 1) * 8) - unusedBits) / 7 ;
ret = GsmAlphabet.gsm7BitPackedToString(data, offset + 1, countSeptets);
break;
case 1:
// UCS2
try {
ret = new String(data,
offset + 1, length - 1, "utf-16");
} catch (UnsupportedEncodingException ex) {
ret = "";
Rlog.e(LOG_TAG,"implausible UnsupportedEncodingException", ex);
}
break;
// unsupported encoding
default:
ret = "";
break;
}
// "Add CI"
// "The MS should add the letters for the Country's Initials and
// a separator (e.g. a space) to the text string"
if ((data[offset] & 0x40) != 0) {
// FIXME(mkf) add country initials here
}
return ret;
}
/**
* Convert a TS 131.102 image instance of code scheme '11' into Bitmap
* @param data The raw data
* @param length The length of image body
* @return The bitmap
*/
public static Bitmap parseToBnW(byte[] data, int length){
int valueIndex = 0;
int width = data[valueIndex++] & 0xFF;
int height = data[valueIndex++] & 0xFF;
int numOfPixels = width*height;
int[] pixels = new int[numOfPixels];
int pixelIndex = 0;
int bitIndex = 7;
byte currentByte = 0x00;
while (pixelIndex < numOfPixels) {
// reassign data and index for every byte (8 bits).
if (pixelIndex % 8 == 0) {
currentByte = data[valueIndex++];
bitIndex = 7;
}
pixels[pixelIndex++] = bitToRGB((currentByte >> bitIndex-- ) & 0x01);
}
if (pixelIndex != numOfPixels) {
Rlog.e(LOG_TAG, "parse end and size error");
}
return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
}
private static int bitToRGB(int bit){
if(bit == 1){
return Color.WHITE;
} else {
return Color.BLACK;
}
}
/**
* a TS 131.102 image instance of code scheme '11' into color Bitmap
*
* @param data The raw data
* @param length the length of image body
* @param transparency with or without transparency
* @return The color bitmap
*/
public static Bitmap parseToRGB(byte[] data, int length,
boolean transparency) {
int valueIndex = 0;
int width = data[valueIndex++] & 0xFF;
int height = data[valueIndex++] & 0xFF;
int bits = data[valueIndex++] & 0xFF;
int colorNumber = data[valueIndex++] & 0xFF;
int clutOffset = ((data[valueIndex++] & 0xFF) << 8)
| (data[valueIndex++] & 0xFF);
int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber);
if (true == transparency) {
colorIndexArray[colorNumber - 1] = Color.TRANSPARENT;
}
int[] resultArray = null;
if (0 == (8 % bits)) {
resultArray = mapTo2OrderBitColor(data, valueIndex,
(width * height), colorIndexArray, bits);
} else {
resultArray = mapToNon2OrderBitColor(data, valueIndex,
(width * height), colorIndexArray, bits);
}
return Bitmap.createBitmap(resultArray, width, height,
Bitmap.Config.RGB_565);
}
private static int[] mapTo2OrderBitColor(byte[] data, int valueIndex,
int length, int[] colorArray, int bits) {
if (0 != (8 % bits)) {
Rlog.e(LOG_TAG, "not event number of color");
return mapToNon2OrderBitColor(data, valueIndex, length, colorArray,
bits);
}
int mask = 0x01;
switch (bits) {
case 1:
mask = 0x01;
break;
case 2:
mask = 0x03;
break;
case 4:
mask = 0x0F;
break;
case 8:
mask = 0xFF;
break;
}
int[] resultArray = new int[length];
int resultIndex = 0;
int run = 8 / bits;
while (resultIndex < length) {
byte tempByte = data[valueIndex++];
for (int runIndex = 0; runIndex < run; ++runIndex) {
int offset = run - runIndex - 1;
resultArray[resultIndex++] = colorArray[(tempByte >> (offset * bits))
& mask];
}
}
return resultArray;
}
private static int[] mapToNon2OrderBitColor(byte[] data, int valueIndex,
int length, int[] colorArray, int bits) {
if (0 == (8 % bits)) {
Rlog.e(LOG_TAG, "not odd number of color");
return mapTo2OrderBitColor(data, valueIndex, length, colorArray,
bits);
}
int[] resultArray = new int[length];
// TODO fix me:
return resultArray;
}
private static int[] getCLUT(byte[] rawData, int offset, int number) {
if (null == rawData) {
return null;
}
int[] result = new int[number];
int endIndex = offset + (number * 3); // 1 color use 3 bytes
int valueIndex = offset;
int colorIndex = 0;
int alpha = 0xff << 24;
do {
result[colorIndex++] = alpha
| ((rawData[valueIndex++] & 0xFF) << 16)
| ((rawData[valueIndex++] & 0xFF) << 8)
| ((rawData[valueIndex++] & 0xFF));
} while (valueIndex < endIndex);
return result;
}
}