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