• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package com.android.mms.ui;
19 
20 import java.util.regex.Pattern;
21 
22 import android.content.ContentUris;
23 import android.content.Context;
24 import android.database.Cursor;
25 import android.net.Uri;
26 import android.provider.Telephony.Mms;
27 import android.provider.Telephony.MmsSms;
28 import android.provider.Telephony.Sms;
29 import android.text.TextUtils;
30 import android.util.Log;
31 
32 import com.android.mms.LogTag;
33 import com.android.mms.MmsApp;
34 import com.android.mms.R;
35 import com.android.mms.data.Contact;
36 import com.android.mms.model.SlideModel;
37 import com.android.mms.model.SlideshowModel;
38 import com.android.mms.model.TextModel;
39 import com.android.mms.ui.MessageListAdapter.ColumnsMap;
40 import com.android.mms.util.AddressUtils;
41 import com.android.mms.util.DownloadManager;
42 import com.android.mms.util.ItemLoadedCallback;
43 import com.android.mms.util.ItemLoadedFuture;
44 import com.android.mms.util.PduLoaderManager;
45 import com.google.android.mms.MmsException;
46 import com.google.android.mms.pdu.EncodedStringValue;
47 import com.google.android.mms.pdu.MultimediaMessagePdu;
48 import com.google.android.mms.pdu.NotificationInd;
49 import com.google.android.mms.pdu.PduHeaders;
50 import com.google.android.mms.pdu.PduPersister;
51 import com.google.android.mms.pdu.RetrieveConf;
52 import com.google.android.mms.pdu.SendReq;
53 
54 /**
55  * Mostly immutable model for an SMS/MMS message.
56  *
57  * <p>The only mutable field is the cached formatted message member,
58  * the formatting of which is done outside this model in MessageListItem.
59  */
60 public class MessageItem {
61     private static String TAG = "MessageItem";
62 
63     public enum DeliveryStatus  { NONE, INFO, FAILED, PENDING, RECEIVED }
64 
65     public static int ATTACHMENT_TYPE_NOT_LOADED = -1;
66 
67     final Context mContext;
68     final String mType;
69     final long mMsgId;
70     final int mBoxId;
71 
72     DeliveryStatus mDeliveryStatus;
73     boolean mReadReport;
74     boolean mLocked;            // locked to prevent auto-deletion
75 
76     String mTimestamp;
77     String mAddress;
78     String mContact;
79     String mBody; // Body of SMS, first text of MMS.
80     String mTextContentType; // ContentType of text of MMS.
81     Pattern mHighlight; // portion of message to highlight (from search)
82 
83     // The only non-immutable field.  Not synchronized, as access will
84     // only be from the main GUI thread.  Worst case if accessed from
85     // another thread is it'll return null and be set again from that
86     // thread.
87     CharSequence mCachedFormattedMessage;
88 
89     // The last message is cached above in mCachedFormattedMessage. In the latest design, we
90     // show "Sending..." in place of the timestamp when a message is being sent. mLastSendingState
91     // is used to keep track of the last sending state so that if the current sending state is
92     // different, we can clear the message cache so it will get rebuilt and recached.
93     boolean mLastSendingState;
94 
95     // Fields for MMS only.
96     Uri mMessageUri;
97     int mMessageType;
98     int mAttachmentType;
99     String mSubject;
100     SlideshowModel mSlideshow;
101     int mMessageSize;
102     int mErrorType;
103     int mErrorCode;
104     int mMmsStatus;
105     Cursor mCursor;
106     ColumnsMap mColumnsMap;
107     private PduLoadedCallback mPduLoadedCallback;
108     private ItemLoadedFuture mItemLoadedFuture;
109 
MessageItem(Context context, String type, final Cursor cursor, final ColumnsMap columnsMap, Pattern highlight)110     MessageItem(Context context, String type, final Cursor cursor,
111             final ColumnsMap columnsMap, Pattern highlight) throws MmsException {
112         mContext = context;
113         mMsgId = cursor.getLong(columnsMap.mColumnMsgId);
114         mHighlight = highlight;
115         mType = type;
116         mCursor = cursor;
117         mColumnsMap = columnsMap;
118 
119         if ("sms".equals(type)) {
120             mReadReport = false; // No read reports in sms
121 
122             long status = cursor.getLong(columnsMap.mColumnSmsStatus);
123             if (status == Sms.STATUS_NONE) {
124                 // No delivery report requested
125                 mDeliveryStatus = DeliveryStatus.NONE;
126             } else if (status >= Sms.STATUS_FAILED) {
127                 // Failure
128                 mDeliveryStatus = DeliveryStatus.FAILED;
129             } else if (status >= Sms.STATUS_PENDING) {
130                 // Pending
131                 mDeliveryStatus = DeliveryStatus.PENDING;
132             } else {
133                 // Success
134                 mDeliveryStatus = DeliveryStatus.RECEIVED;
135             }
136 
137             mMessageUri = ContentUris.withAppendedId(Sms.CONTENT_URI, mMsgId);
138             // Set contact and message body
139             mBoxId = cursor.getInt(columnsMap.mColumnSmsType);
140             mAddress = cursor.getString(columnsMap.mColumnSmsAddress);
141             if (Sms.isOutgoingFolder(mBoxId)) {
142                 String meString = context.getString(
143                         R.string.messagelist_sender_self);
144 
145                 mContact = meString;
146             } else {
147                 // For incoming messages, the ADDRESS field contains the sender.
148                 mContact = Contact.get(mAddress, false).getName();
149             }
150             mBody = cursor.getString(columnsMap.mColumnSmsBody);
151 
152             // Unless the message is currently in the progress of being sent, it gets a time stamp.
153             if (!isOutgoingMessage()) {
154                 // Set "received" or "sent" time stamp
155                 long date = cursor.getLong(columnsMap.mColumnSmsDate);
156                 mTimestamp = MessageUtils.formatTimeStampString(context, date);
157             }
158 
159             mLocked = cursor.getInt(columnsMap.mColumnSmsLocked) != 0;
160             mErrorCode = cursor.getInt(columnsMap.mColumnSmsErrorCode);
161         } else if ("mms".equals(type)) {
162             mMessageUri = ContentUris.withAppendedId(Mms.CONTENT_URI, mMsgId);
163             mBoxId = cursor.getInt(columnsMap.mColumnMmsMessageBox);
164             mMessageType = cursor.getInt(columnsMap.mColumnMmsMessageType);
165             mErrorType = cursor.getInt(columnsMap.mColumnMmsErrorType);
166             String subject = cursor.getString(columnsMap.mColumnMmsSubject);
167             if (!TextUtils.isEmpty(subject)) {
168                 EncodedStringValue v = new EncodedStringValue(
169                         cursor.getInt(columnsMap.mColumnMmsSubjectCharset),
170                         PduPersister.getBytes(subject));
171                 mSubject = v.getString();
172             }
173             mLocked = cursor.getInt(columnsMap.mColumnMmsLocked) != 0;
174             mSlideshow = null;
175             mAttachmentType = ATTACHMENT_TYPE_NOT_LOADED;
176             mDeliveryStatus = DeliveryStatus.NONE;
177             mReadReport = false;
178             mBody = null;
179             mMessageSize = 0;
180             mTextContentType = null;
181             mTimestamp = null;
182             mMmsStatus = cursor.getInt(columnsMap.mColumnMmsStatus);
183 
184             // Start an async load of the pdu. If the pdu is already loaded, the callback
185             // will get called immediately
186             boolean loadSlideshow = mMessageType != PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
187 
188             mItemLoadedFuture = MmsApp.getApplication().getPduLoaderManager()
189                     .getPdu(mMessageUri, loadSlideshow,
190                     new PduLoadedMessageItemCallback());
191 
192         } else {
193             throw new MmsException("Unknown type of the message: " + type);
194         }
195     }
196 
interpretFrom(EncodedStringValue from, Uri messageUri)197     private void interpretFrom(EncodedStringValue from, Uri messageUri) {
198         if (from != null) {
199             mAddress = from.getString();
200         } else {
201             // In the rare case when getting the "from" address from the pdu fails,
202             // (e.g. from == null) fall back to a slower, yet more reliable method of
203             // getting the address from the "addr" table. This is what the Messaging
204             // notification system uses.
205             mAddress = AddressUtils.getFrom(mContext, messageUri);
206         }
207         mContact = TextUtils.isEmpty(mAddress) ? "" : Contact.get(mAddress, false).getName();
208     }
209 
isMms()210     public boolean isMms() {
211         return mType.equals("mms");
212     }
213 
isSms()214     public boolean isSms() {
215         return mType.equals("sms");
216     }
217 
isDownloaded()218     public boolean isDownloaded() {
219         return (mMessageType != PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
220     }
221 
isOutgoingMessage()222     public boolean isOutgoingMessage() {
223         boolean isOutgoingMms = isMms() && (mBoxId == Mms.MESSAGE_BOX_OUTBOX);
224         boolean isOutgoingSms = isSms()
225                                     && ((mBoxId == Sms.MESSAGE_TYPE_FAILED)
226                                             || (mBoxId == Sms.MESSAGE_TYPE_OUTBOX)
227                                             || (mBoxId == Sms.MESSAGE_TYPE_QUEUED));
228         return isOutgoingMms || isOutgoingSms;
229     }
230 
isSending()231     public boolean isSending() {
232         return !isFailedMessage() && isOutgoingMessage();
233     }
234 
isFailedMessage()235     public boolean isFailedMessage() {
236         boolean isFailedMms = isMms()
237                             && (mErrorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT);
238         boolean isFailedSms = isSms()
239                             && (mBoxId == Sms.MESSAGE_TYPE_FAILED);
240         return isFailedMms || isFailedSms;
241     }
242 
243     // Note: This is the only mutable field in this class.  Think of
244     // mCachedFormattedMessage as a C++ 'mutable' field on a const
245     // object, with this being a lazy accessor whose logic to set it
246     // is outside the class for model/view separation reasons.  In any
247     // case, please keep this class conceptually immutable.
setCachedFormattedMessage(CharSequence formattedMessage)248     public void setCachedFormattedMessage(CharSequence formattedMessage) {
249         mCachedFormattedMessage = formattedMessage;
250     }
251 
getCachedFormattedMessage()252     public CharSequence getCachedFormattedMessage() {
253         boolean isSending = isSending();
254         if (isSending != mLastSendingState) {
255             mLastSendingState = isSending;
256             mCachedFormattedMessage = null;         // clear cache so we'll rebuild the message
257                                                     // to show "Sending..." or the sent date.
258         }
259         return mCachedFormattedMessage;
260     }
261 
getBoxId()262     public int getBoxId() {
263         return mBoxId;
264     }
265 
getMessageId()266     public long getMessageId() {
267         return mMsgId;
268     }
269 
getMmsDownloadStatus()270     public int getMmsDownloadStatus() {
271         return mMmsStatus & ~DownloadManager.DEFERRED_MASK;
272     }
273 
274     @Override
toString()275     public String toString() {
276         return "type: " + mType +
277             " box: " + mBoxId +
278             " uri: " + mMessageUri +
279             " address: " + mAddress +
280             " contact: " + mContact +
281             " read: " + mReadReport +
282             " delivery status: " + mDeliveryStatus;
283     }
284 
285     public class PduLoadedMessageItemCallback implements ItemLoadedCallback {
onItemLoaded(Object result, Throwable exception)286         public void onItemLoaded(Object result, Throwable exception) {
287             if (exception != null) {
288                 Log.e(TAG, "PduLoadedMessageItemCallback PDU couldn't be loaded: ", exception);
289                 return;
290             }
291             PduLoaderManager.PduLoaded pduLoaded = (PduLoaderManager.PduLoaded)result;
292             long timestamp = 0L;
293             if (PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND == mMessageType) {
294                 mDeliveryStatus = DeliveryStatus.NONE;
295                 NotificationInd notifInd = (NotificationInd)pduLoaded.mPdu;
296                 interpretFrom(notifInd.getFrom(), mMessageUri);
297                 // Borrow the mBody to hold the URL of the message.
298                 mBody = new String(notifInd.getContentLocation());
299                 mMessageSize = (int) notifInd.getMessageSize();
300                 timestamp = notifInd.getExpiry() * 1000L;
301             } else {
302                 if (mCursor.isClosed()) {
303                     return;
304                 }
305                 MultimediaMessagePdu msg = (MultimediaMessagePdu)pduLoaded.mPdu;
306                 mSlideshow = pduLoaded.mSlideshow;
307                 mAttachmentType = MessageUtils.getAttachmentType(mSlideshow);
308 
309                 if (mMessageType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
310                     if (msg == null) {
311                         interpretFrom(null, mMessageUri);
312                     } else {
313                         RetrieveConf retrieveConf = (RetrieveConf) msg;
314                         interpretFrom(retrieveConf.getFrom(), mMessageUri);
315                         timestamp = retrieveConf.getDate() * 1000L;
316                     }
317                 } else {
318                     // Use constant string for outgoing messages
319                     mContact = mAddress =
320                             mContext.getString(R.string.messagelist_sender_self);
321                     timestamp = msg == null ? 0 : ((SendReq) msg).getDate() * 1000L;
322                 }
323 
324                 SlideModel slide = mSlideshow == null ? null : mSlideshow.get(0);
325                 if ((slide != null) && slide.hasText()) {
326                     TextModel tm = slide.getText();
327                     mBody = tm.getText();
328                     mTextContentType = tm.getContentType();
329                 }
330 
331                 mMessageSize = mSlideshow == null ? 0 : mSlideshow.getTotalMessageSize();
332 
333                 String report = mCursor.getString(mColumnsMap.mColumnMmsDeliveryReport);
334                 if ((report == null) || !mAddress.equals(mContext.getString(
335                         R.string.messagelist_sender_self))) {
336                     mDeliveryStatus = DeliveryStatus.NONE;
337                 } else {
338                     int reportInt;
339                     try {
340                         reportInt = Integer.parseInt(report);
341                         if (reportInt == PduHeaders.VALUE_YES) {
342                             mDeliveryStatus = DeliveryStatus.RECEIVED;
343                         } else {
344                             mDeliveryStatus = DeliveryStatus.NONE;
345                         }
346                     } catch (NumberFormatException nfe) {
347                         Log.e(TAG, "Value for delivery report was invalid.");
348                         mDeliveryStatus = DeliveryStatus.NONE;
349                     }
350                 }
351 
352                 report = mCursor.getString(mColumnsMap.mColumnMmsReadReport);
353                 if ((report == null) || !mAddress.equals(mContext.getString(
354                         R.string.messagelist_sender_self))) {
355                     mReadReport = false;
356                 } else {
357                     int reportInt;
358                     try {
359                         reportInt = Integer.parseInt(report);
360                         mReadReport = (reportInt == PduHeaders.VALUE_YES);
361                     } catch (NumberFormatException nfe) {
362                         Log.e(TAG, "Value for read report was invalid.");
363                         mReadReport = false;
364                     }
365                 }
366             }
367             if (!isOutgoingMessage()) {
368                 if (PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND == mMessageType) {
369                     mTimestamp = mContext.getString(R.string.expire_on,
370                             MessageUtils.formatTimeStampString(mContext, timestamp));
371                 } else {
372                     mTimestamp =  MessageUtils.formatTimeStampString(mContext, timestamp);
373                 }
374             }
375             if (mPduLoadedCallback != null) {
376                 mPduLoadedCallback.onPduLoaded(MessageItem.this);
377             }
378         }
379     }
380 
setOnPduLoaded(PduLoadedCallback pduLoadedCallback)381     public void setOnPduLoaded(PduLoadedCallback pduLoadedCallback) {
382         mPduLoadedCallback = pduLoadedCallback;
383     }
384 
cancelPduLoading()385     public void cancelPduLoading() {
386         if (mItemLoadedFuture != null) {
387             if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
388                 Log.v(TAG, "cancelPduLoading for: " + this);
389             }
390             mItemLoadedFuture.cancel();
391             mItemLoadedFuture = null;
392         }
393     }
394 
395     public interface PduLoadedCallback {
396         /**
397          * Called when this item's pdu and slideshow are finished loading.
398          *
399          * @param messageItem the MessageItem that finished loading.
400          */
onPduLoaded(MessageItem messageItem)401         void onPduLoaded(MessageItem messageItem);
402     }
403 
getSlideshow()404     public SlideshowModel getSlideshow() {
405         return mSlideshow;
406     }
407 }
408