1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony;
18 
19 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
20 import com.android.internal.telephony.SmsConstants;
21 import com.android.internal.telephony.SmsHeader;
22 import java.text.BreakIterator;
23 import java.util.Arrays;
24 
25 import android.provider.Telephony;
26 import android.telephony.SmsMessage;
27 import android.text.Emoji;
28 
29 /**
30  * Base class declaring the specific methods and members for SmsMessage.
31  * {@hide}
32  */
33 public abstract class SmsMessageBase {
34     /** {@hide} The address of the SMSC. May be null */
35     protected String mScAddress;
36 
37     /** {@hide} The address of the sender */
38     protected SmsAddress mOriginatingAddress;
39 
40     /** {@hide} The message body as a string. May be null if the message isn't text */
41     protected String mMessageBody;
42 
43     /** {@hide} */
44     protected String mPseudoSubject;
45 
46     /** {@hide} Non-null if this is an email gateway message */
47     protected String mEmailFrom;
48 
49     /** {@hide} Non-null if this is an email gateway message */
50     protected String mEmailBody;
51 
52     /** {@hide} */
53     protected boolean mIsEmail;
54 
55     /** {@hide} Time when SC (service centre) received the message */
56     protected long mScTimeMillis;
57 
58     /** {@hide} The raw PDU of the message */
59     protected byte[] mPdu;
60 
61     /** {@hide} The raw bytes for the user data section of the message */
62     protected byte[] mUserData;
63 
64     /** {@hide} */
65     protected SmsHeader mUserDataHeader;
66 
67     // "Message Waiting Indication Group"
68     // 23.038 Section 4
69     /** {@hide} */
70     protected boolean mIsMwi;
71 
72     /** {@hide} */
73     protected boolean mMwiSense;
74 
75     /** {@hide} */
76     protected boolean mMwiDontStore;
77 
78     /**
79      * Indicates status for messages stored on the ICC.
80      */
81     protected int mStatusOnIcc = -1;
82 
83     /**
84      * Record index of message in the EF.
85      */
86     protected int mIndexOnIcc = -1;
87 
88     /** TP-Message-Reference - Message Reference of sent message. @hide */
89     public int mMessageRef;
90 
91     // TODO(): This class is duplicated in SmsMessage.java. Refactor accordingly.
92     public static abstract class SubmitPduBase  {
93         public byte[] encodedScAddress; // Null if not applicable.
94         public byte[] encodedMessage;
95 
96         @Override
toString()97         public String toString() {
98             return "SubmitPdu: encodedScAddress = "
99                     + Arrays.toString(encodedScAddress)
100                     + ", encodedMessage = "
101                     + Arrays.toString(encodedMessage);
102         }
103     }
104 
105     /**
106      * Returns the address of the SMS service center that relayed this message
107      * or null if there is none.
108      */
getServiceCenterAddress()109     public String getServiceCenterAddress() {
110         return mScAddress;
111     }
112 
113     /**
114      * Returns the originating address (sender) of this SMS message in String
115      * form or null if unavailable
116      */
getOriginatingAddress()117     public String getOriginatingAddress() {
118         if (mOriginatingAddress == null) {
119             return null;
120         }
121 
122         return mOriginatingAddress.getAddressString();
123     }
124 
125     /**
126      * Returns the originating address, or email from address if this message
127      * was from an email gateway. Returns null if originating address
128      * unavailable.
129      */
getDisplayOriginatingAddress()130     public String getDisplayOriginatingAddress() {
131         if (mIsEmail) {
132             return mEmailFrom;
133         } else {
134             return getOriginatingAddress();
135         }
136     }
137 
138     /**
139      * Returns the message body as a String, if it exists and is text based.
140      * @return message body is there is one, otherwise null
141      */
getMessageBody()142     public String getMessageBody() {
143         return mMessageBody;
144     }
145 
146     /**
147      * Returns the class of this message.
148      */
getMessageClass()149     public abstract SmsConstants.MessageClass getMessageClass();
150 
151     /**
152      * Returns the message body, or email message body if this message was from
153      * an email gateway. Returns null if message body unavailable.
154      */
getDisplayMessageBody()155     public String getDisplayMessageBody() {
156         if (mIsEmail) {
157             return mEmailBody;
158         } else {
159             return getMessageBody();
160         }
161     }
162 
163     /**
164      * Unofficial convention of a subject line enclosed in parens empty string
165      * if not present
166      */
getPseudoSubject()167     public String getPseudoSubject() {
168         return mPseudoSubject == null ? "" : mPseudoSubject;
169     }
170 
171     /**
172      * Returns the service centre timestamp in currentTimeMillis() format
173      */
getTimestampMillis()174     public long getTimestampMillis() {
175         return mScTimeMillis;
176     }
177 
178     /**
179      * Returns true if message is an email.
180      *
181      * @return true if this message came through an email gateway and email
182      *         sender / subject / parsed body are available
183      */
isEmail()184     public boolean isEmail() {
185         return mIsEmail;
186     }
187 
188     /**
189      * @return if isEmail() is true, body of the email sent through the gateway.
190      *         null otherwise
191      */
getEmailBody()192     public String getEmailBody() {
193         return mEmailBody;
194     }
195 
196     /**
197      * @return if isEmail() is true, email from address of email sent through
198      *         the gateway. null otherwise
199      */
getEmailFrom()200     public String getEmailFrom() {
201         return mEmailFrom;
202     }
203 
204     /**
205      * Get protocol identifier.
206      */
getProtocolIdentifier()207     public abstract int getProtocolIdentifier();
208 
209     /**
210      * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
211      * SMS
212      */
isReplace()213     public abstract boolean isReplace();
214 
215     /**
216      * Returns true for CPHS MWI toggle message.
217      *
218      * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
219      *         B.4.2
220      */
isCphsMwiMessage()221     public abstract boolean isCphsMwiMessage();
222 
223     /**
224      * returns true if this message is a CPHS voicemail / message waiting
225      * indicator (MWI) clear message
226      */
isMWIClearMessage()227     public abstract boolean isMWIClearMessage();
228 
229     /**
230      * returns true if this message is a CPHS voicemail / message waiting
231      * indicator (MWI) set message
232      */
isMWISetMessage()233     public abstract boolean isMWISetMessage();
234 
235     /**
236      * returns true if this message is a "Message Waiting Indication Group:
237      * Discard Message" notification and should not be stored.
238      */
isMwiDontStore()239     public abstract boolean isMwiDontStore();
240 
241     /**
242      * returns the user data section minus the user data header if one was
243      * present.
244      */
getUserData()245     public byte[] getUserData() {
246         return mUserData;
247     }
248 
249     /**
250      * Returns an object representing the user data header
251      *
252      * {@hide}
253      */
getUserDataHeader()254     public SmsHeader getUserDataHeader() {
255         return mUserDataHeader;
256     }
257 
258     /**
259      * TODO(cleanup): The term PDU is used in a seemingly non-unique
260      * manner -- for example, what is the difference between this byte
261      * array and the contents of SubmitPdu objects.  Maybe a more
262      * illustrative term would be appropriate.
263      */
264 
265     /**
266      * Returns the raw PDU for the message.
267      */
getPdu()268     public byte[] getPdu() {
269         return mPdu;
270     }
271 
272     /**
273      * For an SMS-STATUS-REPORT message, this returns the status field from
274      * the status report.  This field indicates the status of a previously
275      * submitted SMS, if requested.  See TS 23.040, 9.2.3.15 TP-Status for a
276      * description of values.
277      *
278      * @return 0 indicates the previously sent message was received.
279      *         See TS 23.040, 9.9.2.3.15 for a description of other possible
280      *         values.
281      */
getStatus()282     public abstract int getStatus();
283 
284     /**
285      * Return true iff the message is a SMS-STATUS-REPORT message.
286      */
isStatusReportMessage()287     public abstract boolean isStatusReportMessage();
288 
289     /**
290      * Returns true iff the <code>TP-Reply-Path</code> bit is set in
291      * this message.
292      */
isReplyPathPresent()293     public abstract boolean isReplyPathPresent();
294 
295     /**
296      * Returns the status of the message on the ICC (read, unread, sent, unsent).
297      *
298      * @return the status of the message on the ICC.  These are:
299      *         SmsManager.STATUS_ON_ICC_FREE
300      *         SmsManager.STATUS_ON_ICC_READ
301      *         SmsManager.STATUS_ON_ICC_UNREAD
302      *         SmsManager.STATUS_ON_ICC_SEND
303      *         SmsManager.STATUS_ON_ICC_UNSENT
304      */
getStatusOnIcc()305     public int getStatusOnIcc() {
306         return mStatusOnIcc;
307     }
308 
309     /**
310      * Returns the record index of the message on the ICC (1-based index).
311      * @return the record index of the message on the ICC, or -1 if this
312      *         SmsMessage was not created from a ICC SMS EF record.
313      */
getIndexOnIcc()314     public int getIndexOnIcc() {
315         return mIndexOnIcc;
316     }
317 
parseMessageBody()318     protected void parseMessageBody() {
319         // originatingAddress could be null if this message is from a status
320         // report.
321         if (mOriginatingAddress != null && mOriginatingAddress.couldBeEmailGateway()) {
322             extractEmailAddressFromMessageBody();
323         }
324     }
325 
326     /**
327      * Try to parse this message as an email gateway message
328      * There are two ways specified in TS 23.040 Section 3.8 :
329      *  - SMS message "may have its TP-PID set for Internet electronic mail - MT
330      * SMS format: [<from-address><space>]<message> - "Depending on the
331      * nature of the gateway, the destination/origination address is either
332      * derived from the content of the SMS TP-OA or TP-DA field, or the
333      * TP-OA/TP-DA field contains a generic gateway address and the to/from
334      * address is added at the beginning as shown above." (which is supported here)
335      * - Multiple addresses separated by commas, no spaces, Subject field delimited
336      * by '()' or '##' and '#' Section 9.2.3.24.11 (which are NOT supported here)
337      */
extractEmailAddressFromMessageBody()338     protected void extractEmailAddressFromMessageBody() {
339 
340         /* Some carriers may use " /" delimiter as below
341          *
342          * 1. [x@y][ ]/[subject][ ]/[body]
343          * -or-
344          * 2. [x@y][ ]/[body]
345          */
346          String[] parts = mMessageBody.split("( /)|( )", 2);
347          if (parts.length < 2) return;
348          mEmailFrom = parts[0];
349          mEmailBody = parts[1];
350          mIsEmail = Telephony.Mms.isEmailAddress(mEmailFrom);
351     }
352 
353     /**
354      * Find the next position to start a new fragment of a multipart SMS.
355      *
356      * @param currentPosition current start position of the fragment
357      * @param byteLimit maximum number of bytes in the fragment
358      * @param msgBody text of the SMS in UTF-16 encoding
359      * @return the position to start the next fragment
360      */
findNextUnicodePosition( int currentPosition, int byteLimit, CharSequence msgBody)361     public static int findNextUnicodePosition(
362             int currentPosition, int byteLimit, CharSequence msgBody) {
363         int nextPos = Math.min(currentPosition + byteLimit / 2, msgBody.length());
364         // Check whether the fragment ends in a character boundary. Some characters take 4-bytes
365         // in UTF-16 encoding. Many carriers cannot handle
366         // a fragment correctly if it does not end at a character boundary.
367         if (nextPos < msgBody.length()) {
368             BreakIterator breakIterator = BreakIterator.getCharacterInstance();
369             breakIterator.setText(msgBody.toString());
370             if (!breakIterator.isBoundary(nextPos)) {
371                 int breakPos = breakIterator.preceding(nextPos);
372                 while (breakPos + 4 <= nextPos
373                         && Emoji.isRegionalIndicatorSymbol(
374                             Character.codePointAt(msgBody, breakPos))
375                         && Emoji.isRegionalIndicatorSymbol(
376                             Character.codePointAt(msgBody, breakPos + 2))) {
377                     // skip forward over flags (pairs of Regional Indicator Symbol)
378                     breakPos += 4;
379                 }
380                 if (breakPos > currentPosition) {
381                     nextPos = breakPos;
382                 } else if (Character.isHighSurrogate(msgBody.charAt(nextPos - 1))) {
383                     // no character boundary in this fragment, try to at least land on a code point
384                     nextPos -= 1;
385                 }
386             }
387         }
388         return nextPos;
389     }
390 
391     /**
392      * Calculate the TextEncodingDetails of a message encoded in Unicode.
393      */
calcUnicodeEncodingDetails(CharSequence msgBody)394     public static TextEncodingDetails calcUnicodeEncodingDetails(CharSequence msgBody) {
395         TextEncodingDetails ted = new TextEncodingDetails();
396         int octets = msgBody.length() * 2;
397         ted.codeUnitSize = SmsConstants.ENCODING_16BIT;
398         ted.codeUnitCount = msgBody.length();
399         if (octets > SmsConstants.MAX_USER_DATA_BYTES) {
400             // If EMS is not supported, break down EMS into single segment SMS
401             // and add page info " x/y".
402             // In the case of UCS2 encoding type, we need 8 bytes for this
403             // but we only have 6 bytes from UDH, so truncate the limit for
404             // each segment by 2 bytes (1 char).
405             int maxUserDataBytesWithHeader = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
406             if (!SmsMessage.hasEmsSupport()) {
407                 // make sure total number of segments is less than 10
408                 if (octets <= 9 * (maxUserDataBytesWithHeader - 2)) {
409                     maxUserDataBytesWithHeader -= 2;
410                 }
411             }
412 
413             int pos = 0;  // Index in code units.
414             int msgCount = 0;
415             while (pos < msgBody.length()) {
416                 int nextPos = findNextUnicodePosition(pos, maxUserDataBytesWithHeader,
417                         msgBody);
418                 if (nextPos == msgBody.length()) {
419                     ted.codeUnitsRemaining = pos + maxUserDataBytesWithHeader / 2 -
420                             msgBody.length();
421                 }
422                 pos = nextPos;
423                 msgCount++;
424             }
425             ted.msgCount = msgCount;
426         } else {
427             ted.msgCount = 1;
428             ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets) / 2;
429         }
430 
431         return ted;
432     }
433 }
434