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