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