MessageItem.java revision 53628a1ade48d676cf946e55659c4b9bb7811b91
1/*
2 * Copyright (C) 2008 Esmertec AG.
3 * Copyright (C) 2008 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.mms.ui;
19
20import com.android.mms.R;
21import com.android.mms.data.Contact;
22import com.android.mms.model.SlideModel;
23import com.android.mms.model.SlideshowModel;
24import com.android.mms.model.TextModel;
25import com.android.mms.ui.MessageListAdapter.ColumnsMap;
26import com.android.mms.util.AddressUtils;
27import com.google.android.mms.MmsException;
28import com.google.android.mms.pdu.EncodedStringValue;
29import com.google.android.mms.pdu.MultimediaMessagePdu;
30import com.google.android.mms.pdu.NotificationInd;
31import com.google.android.mms.pdu.PduHeaders;
32import com.google.android.mms.pdu.PduPersister;
33import com.google.android.mms.pdu.RetrieveConf;
34import com.google.android.mms.pdu.SendReq;
35
36import android.content.ContentUris;
37import android.content.Context;
38import android.database.Cursor;
39import android.net.Uri;
40import android.provider.Telephony.Mms;
41import android.provider.Telephony.Sms;
42import android.text.TextUtils;
43import android.util.Log;
44
45/**
46 * Mostly immutable model for an SMS/MMS message.
47 *
48 * <p>The only mutable field is the cached formatted message member,
49 * the formatting of which is done outside this model in MessageListItem.
50 */
51public class MessageItem {
52    private static String TAG = "MessageItem";
53
54    final Context mContext;
55    final String mType;
56    final long mMsgId;
57    final int mBoxId;
58
59    boolean mDeliveryReport;
60    boolean mReadReport;
61    boolean mLocked;            // locked to prevent auto-deletion
62
63    String mTimestamp;
64    String mAddress;
65    String mContact;
66    String mBody; // Body of SMS, first text of MMS.
67    String mHighlight; // portion of message to highlight (from search)
68
69    // The only non-immutable field.  Not synchronized, as access will
70    // only be from the main GUI thread.  Worst case if accessed from
71    // another thread is it'll return null and be set again from that
72    // thread.
73    CharSequence mCachedFormattedMessage;
74
75    // Fields for MMS only.
76    Uri mMessageUri;
77    int mMessageType;
78    int mAttachmentType;
79    String mSubject;
80    SlideshowModel mSlideshow;
81    int mMessageSize;
82    int mErrorType;
83    int mErrorCode;
84
85    MessageItem(Context context, String type, Cursor cursor,
86            ColumnsMap columnsMap, String highlight) throws MmsException {
87        mContext = context;
88        mMsgId = cursor.getLong(columnsMap.mColumnMsgId);
89        mHighlight = highlight != null ? highlight.toLowerCase() : null;
90        mType = type;
91
92        if ("sms".equals(type)) {
93            mReadReport = false; // No read reports in sms
94            mDeliveryReport = (cursor.getLong(columnsMap.mColumnSmsStatus)
95                    != Sms.STATUS_NONE);
96            mMessageUri = ContentUris.withAppendedId(Sms.CONTENT_URI, mMsgId);
97            // Set contact and message body
98            mBoxId = cursor.getInt(columnsMap.mColumnSmsType);
99            mAddress = cursor.getString(columnsMap.mColumnSmsAddress);
100            if (Sms.isOutgoingFolder(mBoxId)) {
101                String meString = context.getString(
102                        R.string.messagelist_sender_self);
103
104                mContact = meString;
105            } else {
106                // For incoming messages, the ADDRESS field contains the sender.
107                mContact = Contact.get(mAddress, true).getName();
108            }
109            mBody = cursor.getString(columnsMap.mColumnSmsBody);
110
111            if (!isOutgoingMessage()) {
112                // Set "sent" time stamp
113                long date = cursor.getLong(columnsMap.mColumnSmsDate);
114                mTimestamp = String.format(context.getString(R.string.sent_on),
115                        MessageUtils.formatTimeStampString(context, date));
116            }
117
118            mLocked = cursor.getInt(columnsMap.mColumnSmsLocked) != 0;
119            mErrorCode = cursor.getInt(columnsMap.mColumnSmsErrorCode);
120        } else if ("mms".equals(type)) {
121            mMessageUri = ContentUris.withAppendedId(Mms.CONTENT_URI, mMsgId);
122            mBoxId = cursor.getInt(columnsMap.mColumnMmsMessageBox);
123            mMessageType = cursor.getInt(columnsMap.mColumnMmsMessageType);
124            mErrorType = cursor.getInt(columnsMap.mColumnMmsErrorType);
125            String subject = cursor.getString(columnsMap.mColumnMmsSubject);
126            if (!TextUtils.isEmpty(subject)) {
127                EncodedStringValue v = new EncodedStringValue(
128                        cursor.getInt(columnsMap.mColumnMmsSubjectCharset),
129                        PduPersister.getBytes(subject));
130                mSubject = v.getString();
131            }
132            mLocked = cursor.getInt(columnsMap.mColumnMmsLocked) != 0;
133
134            long timestamp = 0L;
135            PduPersister p = PduPersister.getPduPersister(mContext);
136            if (PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND == mMessageType) {
137                mDeliveryReport = false;
138                NotificationInd notifInd = (NotificationInd) p.load(mMessageUri);
139                interpretFrom(notifInd.getFrom(), mMessageUri);
140                // Borrow the mBody to hold the URL of the message.
141                mBody = new String(notifInd.getContentLocation());
142                mMessageSize = (int) notifInd.getMessageSize();
143                timestamp = notifInd.getExpiry() * 1000L;
144            } else {
145                MultimediaMessagePdu msg = (MultimediaMessagePdu) p.load(mMessageUri);
146                mSlideshow = SlideshowModel.createFromPduBody(context, msg.getBody());
147                mAttachmentType = MessageUtils.getAttachmentType(mSlideshow);
148
149                if (mMessageType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
150                    RetrieveConf retrieveConf = (RetrieveConf) msg;
151                    interpretFrom(retrieveConf.getFrom(), mMessageUri);
152                    timestamp = retrieveConf.getDate() * 1000L;
153                } else {
154                    // Use constant string for outgoing messages
155                    mContact = mAddress = context.getString(R.string.messagelist_sender_self);
156                    timestamp = ((SendReq) msg).getDate() * 1000L;
157                }
158
159
160                String report = cursor.getString(columnsMap.mColumnMmsDeliveryReport);
161                if ((report == null) || !mAddress.equals(context.getString(
162                        R.string.messagelist_sender_self))) {
163                    mDeliveryReport = false;
164                } else {
165                    int reportInt;
166                    try {
167                        reportInt = Integer.parseInt(report);
168                        mDeliveryReport =
169                            (reportInt == PduHeaders.VALUE_YES);
170                    } catch (NumberFormatException nfe) {
171                        Log.e(TAG, "Value for delivery report was invalid.");
172                        mDeliveryReport = false;
173                    }
174                }
175
176                report = cursor.getString(columnsMap.mColumnMmsReadReport);
177                if ((report == null) || !mAddress.equals(context.getString(
178                        R.string.messagelist_sender_self))) {
179                    mReadReport = false;
180                } else {
181                    int reportInt;
182                    try {
183                        reportInt = Integer.parseInt(report);
184                        mReadReport = (reportInt == PduHeaders.VALUE_YES);
185                    } catch (NumberFormatException nfe) {
186                        Log.e(TAG, "Value for read report was invalid.");
187                        mReadReport = false;
188                    }
189                }
190
191                SlideModel slide = mSlideshow.get(0);
192                if ((slide != null) && slide.hasText()) {
193                    TextModel tm = slide.getText();
194                    if (tm.isDrmProtected()) {
195                        mBody = mContext.getString(R.string.drm_protected_text);
196                    } else {
197                        mBody = tm.getText();
198                    }
199                }
200
201                mMessageSize = mSlideshow.getCurrentMessageSize();
202            }
203
204            if (!isOutgoingMessage()) {
205                mTimestamp = context.getString(getTimestampStrId(),
206                        MessageUtils.formatTimeStampString(context, timestamp));
207            }
208        } else {
209            throw new MmsException("Unknown type of the message: " + type);
210        }
211    }
212
213    private void interpretFrom(EncodedStringValue from, Uri messageUri) {
214        if (from != null) {
215            mAddress = from.getString();
216        } else {
217            // In the rare case when getting the "from" address from the pdu fails,
218            // (e.g. from == null) fall back to a slower, yet more reliable method of
219            // getting the address from the "addr" table. This is what the Messaging
220            // notification system uses.
221            mAddress = AddressUtils.getFrom(mContext, messageUri);
222        }
223        mContact = TextUtils.isEmpty(mAddress) ? "" : Contact.get(mAddress, true).getName();
224    }
225
226    private int getTimestampStrId() {
227        if (PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND == mMessageType) {
228            return R.string.expire_on;
229        } else {
230            return R.string.sent_on;
231        }
232    }
233
234    public boolean isMms() {
235        return mType.equals("mms");
236    }
237
238    public boolean isSms() {
239        return mType.equals("sms");
240    }
241
242    public boolean isDownloaded() {
243        return (mMessageType != PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
244    }
245
246    public boolean isOutgoingMessage() {
247        boolean isOutgoingMms = isMms() && (mBoxId == Mms.MESSAGE_BOX_OUTBOX);
248        boolean isOutgoingSms = isSms()
249                                    && ((mBoxId == Sms.MESSAGE_TYPE_FAILED)
250                                            || (mBoxId == Sms.MESSAGE_TYPE_OUTBOX)
251                                            || (mBoxId == Sms.MESSAGE_TYPE_QUEUED));
252        return isOutgoingMms || isOutgoingSms;
253    }
254
255    // Note: This is the only mutable field in this class.  Think of
256    // mCachedFormattedMessage as a C++ 'mutable' field on a const
257    // object, with this being a lazy accessor whose logic to set it
258    // is outside the class for model/view separation reasons.  In any
259    // case, please keep this class conceptually immutable.
260    public void setCachedFormattedMessage(CharSequence formattedMessage) {
261        mCachedFormattedMessage = formattedMessage;
262    }
263
264    public CharSequence getCachedFormattedMessage() {
265        return mCachedFormattedMessage;
266    }
267
268    public int getBoxId() {
269        return mBoxId;
270    }
271
272    @Override
273    public String toString() {
274        return "type: " + mType +
275            " box: " + mBoxId +
276            " uri: " + mMessageUri +
277            " address: " + mAddress +
278            " contact: " + mContact +
279            " read: " + mReadReport +
280            " delivery report: " + mDeliveryReport;
281    }
282}
283