SendersView.java revision 571de2212f311cc4a514ffa74e7585a2df1fb907
1/* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to 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.mail.browse; 19 20import android.content.Context; 21import android.content.res.Resources; 22import android.graphics.Typeface; 23import android.text.SpannableStringBuilder; 24import android.text.TextUtils; 25import android.text.style.CharacterStyle; 26import android.text.style.ForegroundColorSpan; 27import android.text.style.StyleSpan; 28import android.text.util.Rfc822Token; 29import android.text.util.Rfc822Tokenizer; 30import android.util.AttributeSet; 31import android.widget.TextView; 32 33import com.android.mail.R; 34import com.android.mail.providers.Address; 35import com.android.mail.providers.Conversation; 36import com.android.mail.providers.ConversationInfo; 37import com.android.mail.providers.MessageInfo; 38import com.android.mail.utils.Utils; 39 40import java.util.regex.Pattern; 41 42public class SendersView extends TextView { 43 public static final int DEFAULT_FORMATTING = 0; 44 public static final int MERGED_FORMATTING = 1; 45 public static String SENDERS_VERSION_SEPARATOR = "^**^"; 46 CharacterStyle sNormalTextStyle = new StyleSpan(Typeface.NORMAL); 47 public static Pattern SENDERS_VERSION_SEPARATOR_PATTERN = Pattern.compile("\\^\\*\\*\\^"); 48 private int mFormatVersion = -1; 49 private ForegroundColorSpan sLightTextStyle; 50 private int DRAFT_TEXT_COLOR; 51 private int LIGHT_TEXT_COLOR; 52 53 public SendersView(Context context) { 54 this(context, null); 55 } 56 57 public SendersView(Context context, AttributeSet attrs) { 58 this(context, attrs, -1); 59 } 60 61 public SendersView(Context context, AttributeSet attrs, int defStyle) { 62 super(context, attrs, defStyle); 63 Resources res = context.getResources(); 64 LIGHT_TEXT_COLOR = res.getColor(R.color.light_text_color); 65 DRAFT_TEXT_COLOR = res.getColor(R.color.drafts); 66 sLightTextStyle = new ForegroundColorSpan(LIGHT_TEXT_COLOR); 67 } 68 69 public Typeface getTypeface(boolean isUnread) { 70 return mFormatVersion == DEFAULT_FORMATTING ? isUnread ? Typeface.DEFAULT_BOLD 71 : Typeface.DEFAULT : Typeface.DEFAULT; 72 } 73 74 public void formatSenders(ConversationItemViewModel header, boolean isUnread, int mode) { 75 if (TextUtils.isEmpty(header.conversation.senders) 76 && header.conversation.conversationInfo == null) { 77 return; 78 } 79 Conversation conversation = header.conversation; 80 String sendersInfo = conversation.conversationInfo != null ? 81 conversation.conversationInfo.sendersInfo : header.conversation.senders; 82 if (!TextUtils.isEmpty(sendersInfo)) { 83 SendersInfo info = new SendersInfo(sendersInfo); 84 mFormatVersion = info.version; 85 switch (mFormatVersion) { 86 case MERGED_FORMATTING: 87 formatMerged(header, info.text, isUnread, mode); 88 break; 89 case DEFAULT_FORMATTING: 90 default: 91 formatDefault(header, info.text); 92 break; 93 } 94 } else { 95 // We have the properly formatted conversationinfo. Parse and display! 96 format(header, conversation.conversationInfo); 97 } 98 } 99 100 private void format(ConversationItemViewModel header, ConversationInfo conversationInfo) { 101 String[] senders = new String[conversationInfo.messageCount]; 102 for (int i = 0; i < senders.length; i++) { 103 senders[i] = parseSender(conversationInfo.messageInfos.get(i).sender); 104 } 105 generateSenderFragments(header, senders); 106 } 107 108 private String parseSender(String sender) { 109 Rfc822Token[] senderTokens = Rfc822Tokenizer.tokenize(sender); 110 String name; 111 if (senderTokens != null && senderTokens.length > 0) { 112 name = senderTokens[0].getName(); 113 if (TextUtils.isEmpty(name)) { 114 name = senderTokens[0].getAddress(); 115 } 116 return name; 117 } 118 return ""; 119 } 120 121 private void formatDefault(ConversationItemViewModel header, String sendersString) { 122 String[] senders = TextUtils.split(sendersString, Address.ADDRESS_DELIMETER); 123 String[] namesOnly = new String[senders.length]; 124 Rfc822Token[] senderTokens; 125 String display; 126 for (int i = 0; i < senders.length; i++) { 127 senderTokens = Rfc822Tokenizer.tokenize(senders[i]); 128 if (senderTokens != null && senderTokens.length > 0) { 129 display = senderTokens[0].getName(); 130 if (TextUtils.isEmpty(display)) { 131 display = senderTokens[0].getAddress(); 132 } 133 namesOnly[i] = display; 134 } 135 } 136 generateSenderFragments(header, namesOnly); 137 } 138 139 private void generateSenderFragments(ConversationItemViewModel header, String[] names) { 140 header.sendersText = TextUtils.join(Address.ADDRESS_DELIMETER + " ", names); 141 header.addSenderFragment(0, header.sendersText.length(), sNormalTextStyle, true); 142 } 143 144 private void formatMerged(ConversationItemViewModel header, String sendersString, 145 boolean isUnread, int mode) { 146 SpannableStringBuilder sendersBuilder = new SpannableStringBuilder(); 147 SpannableStringBuilder statusBuilder = new SpannableStringBuilder(); 148 Utils.getStyledSenderSnippet(getContext(), sendersString, sendersBuilder, 149 statusBuilder, ConversationItemViewCoordinates.getSubjectLength(getContext(), mode, 150 header.folderDisplayer.hasVisibleFolders(), 151 header.conversation.hasAttachments), false, false, header.hasDraftMessage); 152 header.sendersText = sendersBuilder.toString(); 153 154 CharacterStyle[] spans = sendersBuilder.getSpans(0, sendersBuilder.length(), 155 CharacterStyle.class); 156 header.clearSenderFragments(); 157 int lastPosition = 0; 158 CharacterStyle style = sNormalTextStyle; 159 if (spans != null) { 160 for (CharacterStyle span : spans) { 161 style = span; 162 int start = sendersBuilder.getSpanStart(style); 163 int end = sendersBuilder.getSpanEnd(style); 164 if (start > lastPosition) { 165 header.addSenderFragment(lastPosition, start, sNormalTextStyle, false); 166 } 167 // From instructions won't be updated until the next sync. So we 168 // have to override the text style here to be consistent with 169 // the background color. 170 if (isUnread) { 171 header.addSenderFragment(start, end, style, false); 172 } else { 173 header.addSenderFragment(start, end, sNormalTextStyle, false); 174 } 175 lastPosition = end; 176 } 177 } 178 if (lastPosition < sendersBuilder.length()) { 179 style = sLightTextStyle; 180 header.addSenderFragment(lastPosition, sendersBuilder.length(), style, true); 181 } 182 if (statusBuilder.length() > 0) { 183 if (header.sendersText.length() > 0) { 184 header.sendersText = header.sendersText.concat(", "); 185 186 // Extend the last fragment to include the comma. 187 int lastIndex = header.senderFragments.size() - 1; 188 int start = header.senderFragments.get(lastIndex).start; 189 int end = header.senderFragments.get(lastIndex).end + 2; 190 style = header.senderFragments.get(lastIndex).style; 191 192 // The new fragment is only fixed if the previous fragment 193 // is fixed. 194 boolean isFixed = header.senderFragments.get(lastIndex).isFixed; 195 196 // Remove the old fragment. 197 header.senderFragments.remove(lastIndex); 198 199 // Add new fragment. 200 header.addSenderFragment(start, end, style, isFixed); 201 } 202 int pos = header.sendersText.length(); 203 header.sendersText = header.sendersText.concat(statusBuilder.toString()); 204 header.addSenderFragment(pos, header.sendersText.length(), new ForegroundColorSpan( 205 DRAFT_TEXT_COLOR), true); 206 } 207 } 208 209 public static class SendersInfo { 210 public int version; 211 public String text; 212 213 public SendersInfo(String toParse) { 214 if (TextUtils.isEmpty(toParse)) { 215 version = 0; 216 text = ""; 217 } else { 218 String[] splits = TextUtils.split(toParse, SENDERS_VERSION_SEPARATOR_PATTERN); 219 if (splits == null || splits.length < 2) { 220 version = SendersView.DEFAULT_FORMATTING; 221 text = toParse; 222 } else { 223 version = Integer.parseInt(splits[0]); 224 text = splits[1]; 225 } 226 } 227 } 228 } 229} 230