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