Files
frameworks_base/telephony/java/com/android/internal/telephony/SmsMessageBase.java
Mengjun Leng 43c7335deb SMS: Add a compatible API getRecipientAddress for 3GPP2 SMS
The method getDisplayOriginatingAddress() return null for submit
pdu of 3GPP, it will cause applicatoin can not get address of sent
message. Thread ID is unable to be created for this invalid message
record.

Add an API getRecipientAddress() for SubmitPdu in SmsMessage.

Bug: 73012819
Change-Id: Ib3e98ad3f115038656db1ea7ff5b05491b28b85d
2019-02-12 19:54:05 -08:00

477 lines
15 KiB
Java

/*
* 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.annotation.UnsupportedAppUsage;
import android.os.Build;
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 */
@UnsupportedAppUsage
protected String mScAddress;
/** {@hide} The address of the sender */
@UnsupportedAppUsage
protected SmsAddress mOriginatingAddress;
/** {@hide} The address of the receiver */
protected SmsAddress mRecipientAddress;
/** {@hide} The message body as a string. May be null if the message isn't text */
@UnsupportedAppUsage
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 */
@UnsupportedAppUsage
protected byte[] mPdu;
/** {@hide} The raw bytes for the user data section of the message */
protected byte[] mUserData;
/** {@hide} */
@UnsupportedAppUsage
protected SmsHeader mUserDataHeader;
// "Message Waiting Indication Group"
// 23.038 Section 4
/** {@hide} */
@UnsupportedAppUsage
protected boolean mIsMwi;
/** {@hide} */
@UnsupportedAppUsage
protected boolean mMwiSense;
/** {@hide} */
@UnsupportedAppUsage
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 */
@UnsupportedAppUsage
public int mMessageRef;
// TODO(): This class is duplicated in SmsMessage.java. Refactor accordingly.
public static abstract class SubmitPduBase {
@UnsupportedAppUsage
public byte[] encodedScAddress; // Null if not applicable.
@UnsupportedAppUsage
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.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public String getServiceCenterAddress() {
return mScAddress;
}
/**
* Returns the originating address (sender) of this SMS message in String
* form or null if unavailable
*/
@UnsupportedAppUsage
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.
*/
@UnsupportedAppUsage
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
*/
@UnsupportedAppUsage
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.
*/
@UnsupportedAppUsage
public String getDisplayMessageBody() {
if (mIsEmail) {
return mEmailBody;
} else {
return getMessageBody();
}
}
/**
* Unofficial convention of a subject line enclosed in parens empty string
* if not present
*/
@UnsupportedAppUsage
public String getPseudoSubject() {
return mPseudoSubject == null ? "" : mPseudoSubject;
}
/**
* Returns the service centre timestamp in currentTimeMillis() format
*/
@UnsupportedAppUsage
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.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public abstract int getProtocolIdentifier();
/**
* See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
* SMS
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
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.
*/
@UnsupportedAppUsage
public byte[] getUserData() {
return mUserData;
}
/**
* Returns an object representing the user data header
*
* {@hide}
*/
@UnsupportedAppUsage
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.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public abstract int getStatus();
/**
* Return true iff the message is a SMS-STATUS-REPORT message.
*/
@UnsupportedAppUsage
public abstract boolean isStatusReportMessage();
/**
* Returns true iff the <code>TP-Reply-Path</code> bit is set in
* this message.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
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;
}
/**
* {@hide}
* Returns the receiver address of this SMS message in String
* form or null if unavailable
*/
public String getRecipientAddress() {
if (mRecipientAddress == null) {
return null;
}
return mRecipientAddress.getAddressString();
}
}