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