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