Mindy Pereira | 7b56a61 | 2011-12-14 12:32:28 -0800 | [diff] [blame] | 1 | /** |
| 2 | * Copyright (c) 2011, Google Inc. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 16 | |
Andy Huang | 30e2c24 | 2012-01-06 18:14:30 -0800 | [diff] [blame] | 17 | package com.android.mail.utils; |
Mindy Pereira | 7b56a61 | 2011-12-14 12:32:28 -0800 | [diff] [blame] | 18 | |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 19 | import android.content.Context; |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 20 | import android.content.Intent; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 21 | import android.content.res.Resources; |
| 22 | import android.graphics.Typeface; |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 23 | import android.net.Uri; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 24 | import android.text.Html; |
| 25 | import android.text.Spannable; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 26 | import android.text.SpannableString; |
| 27 | import android.text.SpannableStringBuilder; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 28 | import android.text.Spanned; |
| 29 | import android.text.TextUtils; |
| 30 | import android.text.TextUtils.SimpleStringSplitter; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 31 | import android.text.style.CharacterStyle; |
| 32 | import android.text.style.ForegroundColorSpan; |
| 33 | import android.text.style.StyleSpan; |
Mindy Pereira | 326c660 | 2012-01-04 15:32:42 -0800 | [diff] [blame] | 34 | import android.view.View; |
| 35 | import android.view.ViewGroup; |
| 36 | import android.view.View.MeasureSpec; |
Mindy Pereira | 8b99ba4 | 2011-12-16 09:57:18 -0800 | [diff] [blame] | 37 | import android.webkit.WebSettings; |
| 38 | import android.webkit.WebView; |
| 39 | |
Andy Huang | 30e2c24 | 2012-01-06 18:14:30 -0800 | [diff] [blame] | 40 | import com.android.mail.R; |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 41 | import com.android.mail.providers.Account; |
| 42 | import com.android.mail.providers.Folder; |
| 43 | import com.android.mail.providers.UIProvider; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 44 | import com.google.common.collect.Maps; |
| 45 | |
| 46 | import java.util.Map; |
Mindy Pereira | 7b56a61 | 2011-12-14 12:32:28 -0800 | [diff] [blame] | 47 | |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 48 | public class Utils { |
| 49 | /** |
| 50 | * longest extension we recognize is 4 characters (e.g. "html", "docx") |
| 51 | */ |
| 52 | private static final int FILE_EXTENSION_MAX_CHARS = 4; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 53 | private static final Map<Integer, Integer> sPriorityToLength = Maps.newHashMap(); |
| 54 | public static final String SENDER_LIST_TOKEN_ELIDED = "e"; |
| 55 | public static final String SENDER_LIST_TOKEN_NUM_MESSAGES = "n"; |
| 56 | public static final String SENDER_LIST_TOKEN_NUM_DRAFTS = "d"; |
| 57 | public static final String SENDER_LIST_TOKEN_LITERAL = "l"; |
| 58 | public static final String SENDER_LIST_TOKEN_SENDING = "s"; |
| 59 | public static final String SENDER_LIST_TOKEN_SEND_FAILED = "f"; |
| 60 | public static final Character SENDER_LIST_SEPARATOR = '\n'; |
| 61 | public static final SimpleStringSplitter sSenderListSplitter = new SimpleStringSplitter( |
| 62 | SENDER_LIST_SEPARATOR); |
| 63 | public static String[] sSenderFragments = new String[8]; |
Mindy Pereira | 8b99ba4 | 2011-12-16 09:57:18 -0800 | [diff] [blame] | 64 | |
Mindy Pereira | 6349a04 | 2012-01-04 11:25:01 -0800 | [diff] [blame] | 65 | public static final String EXTRA_ACCOUNT = "account"; |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 66 | /* |
| 67 | * Notifies that changes happened. Certain UI components, e.g., widgets, can |
| 68 | * register for this {@link Intent} and update accordingly. However, this |
| 69 | * can be very broad and is NOT the preferred way of getting notification. |
| 70 | */ |
| 71 | // TODO: UI Provider has this notification URI? |
| 72 | public static final String ACTION_NOTIFY_DATASET_CHANGED = |
| 73 | "com.android.mail.ACTION_NOTIFY_DATASET_CHANGED"; |
Mindy Pereira | 6349a04 | 2012-01-04 11:25:01 -0800 | [diff] [blame] | 74 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 75 | /** |
| 76 | * Sets WebView in a restricted mode suitable for email use. |
| 77 | * |
| 78 | * @param webView The WebView to restrict |
| 79 | */ |
| 80 | public static void restrictWebView(WebView webView) { |
Mindy Pereira | 8b99ba4 | 2011-12-16 09:57:18 -0800 | [diff] [blame] | 81 | WebSettings webSettings = webView.getSettings(); |
| 82 | webSettings.setSavePassword(false); |
| 83 | webSettings.setSaveFormData(false); |
| 84 | webSettings.setJavaScriptEnabled(true); |
| 85 | webSettings.setSupportZoom(false); |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 86 | } |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 87 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 88 | /** |
| 89 | * Format a plural string. |
| 90 | * |
| 91 | * @param resource The identity of the resource, which must be a R.plurals |
| 92 | * @param count The number of items. |
| 93 | */ |
| 94 | public static String formatPlural(Context context, int resource, int count) { |
| 95 | CharSequence formatString = context.getResources().getQuantityText(resource, count); |
| 96 | return String.format(formatString.toString(), count); |
| 97 | } |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 98 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 99 | /** |
| 100 | * @return an ellipsized String that's at most maxCharacters long. If the |
| 101 | * text passed is longer, it will be abbreviated. If it contains a |
| 102 | * suffix, the ellipses will be inserted in the middle and the |
| 103 | * suffix will be preserved. |
| 104 | */ |
| 105 | public static String ellipsize(String text, int maxCharacters) { |
| 106 | int length = text.length(); |
| 107 | if (length < maxCharacters) |
| 108 | return text; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 109 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 110 | int realMax = Math.min(maxCharacters, length); |
| 111 | // Preserve the suffix if any |
| 112 | int index = text.lastIndexOf("."); |
| 113 | String extension = "\u2026"; // "..."; |
| 114 | if (index >= 0) { |
| 115 | // Limit the suffix to dot + four characters |
| 116 | if (length - index <= FILE_EXTENSION_MAX_CHARS + 1) { |
| 117 | extension = extension + text.substring(index + 1); |
| 118 | } |
| 119 | } |
| 120 | realMax -= extension.length(); |
| 121 | if (realMax < 0) |
| 122 | realMax = 0; |
| 123 | return text.substring(0, realMax) + extension; |
| 124 | } |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 125 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 126 | /** |
Mindy Pereira | 4ebb916 | 2012-01-03 11:06:19 -0800 | [diff] [blame] | 127 | * Ensures that the given string starts and ends with the double quote |
| 128 | * character. The string is not modified in any way except to add the double |
| 129 | * quote character to start and end if it's not already there. sample -> |
| 130 | * "sample" "sample" -> "sample" ""sample"" -> "sample" |
| 131 | * "sample"" -> "sample" sa"mp"le -> "sa"mp"le" "sa"mp"le" -> "sa"mp"le" |
| 132 | * (empty string) -> "" " -> "" |
| 133 | */ |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 134 | public static String ensureQuotedString(String s) { |
| 135 | if (s == null) { |
| 136 | return null; |
| 137 | } |
| 138 | if (!s.matches("^\".*\"$")) { |
| 139 | return "\"" + s + "\""; |
| 140 | } else { |
| 141 | return s; |
| 142 | } |
| 143 | } |
Mindy Pereira | 4ebb916 | 2012-01-03 11:06:19 -0800 | [diff] [blame] | 144 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 145 | // TODO: Move this to the UI Provider. |
| 146 | private static CharacterStyle sUnreadStyleSpan = null; |
| 147 | private static CharacterStyle sReadStyleSpan; |
| 148 | private static CharacterStyle sDraftsStyleSpan; |
| 149 | private static CharSequence sMeString; |
| 150 | private static CharSequence sDraftSingularString; |
| 151 | private static CharSequence sDraftPluralString; |
| 152 | private static CharSequence sSendingString; |
| 153 | private static CharSequence sSendFailedString; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 154 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 155 | private static int sMaxUnreadCount = -1; |
| 156 | private static String sUnreadText; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 157 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 158 | public static void getStyledSenderSnippet(Context context, String senderInstructions, |
| 159 | SpannableStringBuilder senderBuilder, SpannableStringBuilder statusBuilder, |
| 160 | int maxChars, boolean forceAllUnread, boolean forceAllRead, boolean allowDraft) { |
| 161 | Resources res = context.getResources(); |
| 162 | if (sUnreadStyleSpan == null) { |
| 163 | sUnreadStyleSpan = new StyleSpan(Typeface.BOLD); |
| 164 | sReadStyleSpan = new StyleSpan(Typeface.NORMAL); |
| 165 | sDraftsStyleSpan = new ForegroundColorSpan(res.getColor(R.color.drafts)); |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 166 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 167 | sMeString = context.getText(R.string.me); |
| 168 | sDraftSingularString = res.getQuantityText(R.plurals.draft, 1); |
| 169 | sDraftPluralString = res.getQuantityText(R.plurals.draft, 2); |
| 170 | SpannableString sendingString = new SpannableString(context.getText(R.string.sending)); |
| 171 | sendingString.setSpan(CharacterStyle.wrap(sDraftsStyleSpan), 0, sendingString.length(), |
| 172 | 0); |
| 173 | sSendingString = sendingString; |
| 174 | sSendFailedString = context.getText(R.string.send_failed); |
| 175 | } |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 176 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 177 | getSenderSnippet(senderInstructions, senderBuilder, statusBuilder, maxChars, |
| 178 | sUnreadStyleSpan, sReadStyleSpan, sDraftsStyleSpan, sMeString, |
| 179 | sDraftSingularString, sDraftPluralString, sSendingString, sSendFailedString, |
| 180 | forceAllUnread, forceAllRead, allowDraft); |
| 181 | } |
| 182 | |
| 183 | /** |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 184 | * Uses sender instructions to build a formatted string. |
| 185 | * <p> |
| 186 | * Sender list instructions contain compact information about the sender |
| 187 | * list. Most work that can be done without knowing how much room will be |
| 188 | * availble for the sender list is done when creating the instructions. |
| 189 | * <p> |
| 190 | * The instructions string consists of tokens separated by |
| 191 | * SENDER_LIST_SEPARATOR. Here are the tokens, one per line: |
| 192 | * <ul> |
| 193 | * <li><tt>n</tt></li> |
| 194 | * <li><em>int</em>, the number of non-draft messages in the conversation</li> |
| 195 | * <li><tt>d</tt</li> |
| 196 | * <li><em>int</em>, the number of drafts in the conversation</li> |
| 197 | * <li><tt>l</tt></li> |
| 198 | * <li><em>literal html to be included in the output</em></li> |
| 199 | * <li><tt>s</tt> indicates that the message is sending (in the outbox |
| 200 | * without errors)</li> |
| 201 | * <li><tt>f</tt> indicates that the message failed to send (in the outbox |
| 202 | * with errors)</li> |
| 203 | * <li><em>for each message</em> |
| 204 | * <ul> |
| 205 | * <li><em>int</em>, 0 for read, 1 for unread</li> |
| 206 | * <li><em>int</em>, the priority of the message. Zero is the most important |
| 207 | * </li> |
| 208 | * <li><em>text</em>, the sender text or blank for messages from 'me'</li> |
| 209 | * </ul> |
| 210 | * </li> |
| 211 | * <li><tt>e</tt> to indicate that one or more messages have been elided</li> |
| 212 | * <p> |
| 213 | * The instructions indicate how many messages and drafts are in the |
| 214 | * conversation and then describe the most important messages in order, |
| 215 | * indicating the priority of each message and whether the message is |
| 216 | * unread. |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 217 | * |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 218 | * @param instructions instructions as described above |
| 219 | * @param senderBuilder the SpannableStringBuilder to append to for sender |
| 220 | * information |
| 221 | * @param statusBuilder the SpannableStringBuilder to append to for status |
| 222 | * @param maxChars the number of characters available to display the text |
| 223 | * @param unreadStyle the CharacterStyle for unread messages, or null |
| 224 | * @param draftsStyle the CharacterStyle for draft messages, or null |
| 225 | * @param sendingString the string to use when there are messages scheduled |
| 226 | * to be sent |
| 227 | * @param sendFailedString the string to use when there are messages that |
| 228 | * mailed to send |
| 229 | * @param meString the string to use for messages sent by this user |
| 230 | * @param draftString the string to use for "Draft" |
| 231 | * @param draftPluralString the string to use for "Drafts" |
| 232 | */ |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 233 | public static synchronized void getSenderSnippet(String instructions, |
| 234 | SpannableStringBuilder senderBuilder, SpannableStringBuilder statusBuilder, |
| 235 | int maxChars, CharacterStyle unreadStyle, CharacterStyle readStyle, |
| 236 | CharacterStyle draftsStyle, CharSequence meString, CharSequence draftString, |
| 237 | CharSequence draftPluralString, CharSequence sendingString, |
| 238 | CharSequence sendFailedString, boolean forceAllUnread, boolean forceAllRead, |
| 239 | boolean allowDraft) { |
| 240 | assert !(forceAllUnread && forceAllRead); |
| 241 | boolean unreadStatusIsForced = forceAllUnread || forceAllRead; |
| 242 | boolean forcedUnreadStatus = forceAllUnread; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 243 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 244 | // Measure each fragment. It's ok to iterate over the entire set of |
| 245 | // fragments because it is |
| 246 | // never a long list, even if there are many senders. |
| 247 | final Map<Integer, Integer> priorityToLength = sPriorityToLength; |
| 248 | priorityToLength.clear(); |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 249 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 250 | int maxFoundPriority = Integer.MIN_VALUE; |
| 251 | int numMessages = 0; |
| 252 | int numDrafts = 0; |
| 253 | CharSequence draftsFragment = ""; |
| 254 | CharSequence sendingFragment = ""; |
| 255 | CharSequence sendFailedFragment = ""; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 256 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 257 | sSenderListSplitter.setString(instructions); |
| 258 | int numFragments = 0; |
| 259 | String[] fragments = sSenderFragments; |
| 260 | int currentSize = fragments.length; |
| 261 | while (sSenderListSplitter.hasNext()) { |
| 262 | fragments[numFragments++] = sSenderListSplitter.next(); |
| 263 | if (numFragments == currentSize) { |
| 264 | sSenderFragments = new String[2 * currentSize]; |
| 265 | System.arraycopy(fragments, 0, sSenderFragments, 0, currentSize); |
| 266 | currentSize *= 2; |
| 267 | fragments = sSenderFragments; |
| 268 | } |
| 269 | } |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 270 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 271 | for (int i = 0; i < numFragments;) { |
| 272 | String fragment0 = fragments[i++]; |
| 273 | if ("".equals(fragment0)) { |
| 274 | // This should be the final fragment. |
| 275 | } else if (SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) { |
| 276 | // ignore |
| 277 | } else if (SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) { |
| 278 | numMessages = Integer.valueOf(fragments[i++]); |
| 279 | } else if (SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) { |
| 280 | String numDraftsString = fragments[i++]; |
| 281 | numDrafts = Integer.parseInt(numDraftsString); |
| 282 | draftsFragment = numDrafts == 1 ? draftString : draftPluralString + " (" |
| 283 | + numDraftsString + ")"; |
| 284 | } else if (SENDER_LIST_TOKEN_LITERAL.equals(fragment0)) { |
| 285 | senderBuilder.append(Html.fromHtml(fragments[i++])); |
| 286 | return; |
| 287 | } else if (SENDER_LIST_TOKEN_SENDING.equals(fragment0)) { |
| 288 | sendingFragment = sendingString; |
| 289 | } else if (SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) { |
| 290 | sendFailedFragment = sendFailedString; |
| 291 | } else { |
| 292 | String priorityString = fragments[i++]; |
| 293 | CharSequence nameString = fragments[i++]; |
| 294 | if (nameString.length() == 0) |
| 295 | nameString = meString; |
| 296 | int priority = Integer.parseInt(priorityString); |
| 297 | priorityToLength.put(priority, nameString.length()); |
| 298 | maxFoundPriority = Math.max(maxFoundPriority, priority); |
| 299 | } |
| 300 | } |
| 301 | String numMessagesFragment = (numMessages != 0) ? " \u00A0" |
| 302 | + Integer.toString(numMessages + numDrafts) : ""; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 303 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 304 | // Don't allocate fixedFragment unless we need it |
| 305 | SpannableStringBuilder fixedFragment = null; |
| 306 | int fixedFragmentLength = 0; |
| 307 | if (draftsFragment.length() != 0 && allowDraft) { |
| 308 | if (fixedFragment == null) { |
| 309 | fixedFragment = new SpannableStringBuilder(); |
| 310 | } |
| 311 | fixedFragment.append(draftsFragment); |
| 312 | if (draftsStyle != null) { |
| 313 | fixedFragment.setSpan(CharacterStyle.wrap(draftsStyle), 0, fixedFragment.length(), |
| 314 | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); |
| 315 | } |
| 316 | } |
| 317 | if (sendingFragment.length() != 0) { |
| 318 | if (fixedFragment == null) { |
| 319 | fixedFragment = new SpannableStringBuilder(); |
| 320 | } |
| 321 | if (fixedFragment.length() != 0) |
| 322 | fixedFragment.append(", "); |
| 323 | fixedFragment.append(sendingFragment); |
| 324 | } |
| 325 | if (sendFailedFragment.length() != 0) { |
| 326 | if (fixedFragment == null) { |
| 327 | fixedFragment = new SpannableStringBuilder(); |
| 328 | } |
| 329 | if (fixedFragment.length() != 0) |
| 330 | fixedFragment.append(", "); |
| 331 | fixedFragment.append(sendFailedFragment); |
| 332 | } |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 333 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 334 | if (fixedFragment != null) { |
| 335 | fixedFragmentLength = fixedFragment.length(); |
| 336 | } |
| 337 | maxChars -= fixedFragmentLength; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 338 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 339 | int maxPriorityToInclude = -1; // inclusive |
| 340 | int numCharsUsed = numMessagesFragment.length(); |
| 341 | int numSendersUsed = 0; |
| 342 | while (maxPriorityToInclude < maxFoundPriority) { |
| 343 | if (priorityToLength.containsKey(maxPriorityToInclude + 1)) { |
| 344 | int length = numCharsUsed + priorityToLength.get(maxPriorityToInclude + 1); |
| 345 | if (numCharsUsed > 0) |
| 346 | length += 2; |
| 347 | // We must show at least two senders if they exist. If we don't |
| 348 | // have space for both |
| 349 | // then we will truncate names. |
| 350 | if (length > maxChars && numSendersUsed >= 2) { |
| 351 | break; |
| 352 | } |
| 353 | numCharsUsed = length; |
| 354 | numSendersUsed++; |
| 355 | } |
| 356 | maxPriorityToInclude++; |
| 357 | } |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 358 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 359 | int numCharsToRemovePerWord = 0; |
| 360 | if (numCharsUsed > maxChars) { |
| 361 | numCharsToRemovePerWord = (numCharsUsed - maxChars) / numSendersUsed; |
| 362 | } |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 363 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 364 | String lastFragment = null; |
| 365 | CharacterStyle lastStyle = null; |
| 366 | for (int i = 0; i < numFragments;) { |
| 367 | String fragment0 = fragments[i++]; |
| 368 | if ("".equals(fragment0)) { |
| 369 | // This should be the final fragment. |
| 370 | } else if (SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) { |
| 371 | if (lastFragment != null) { |
| 372 | addStyledFragment(senderBuilder, lastFragment, lastStyle, false); |
| 373 | senderBuilder.append(" "); |
| 374 | addStyledFragment(senderBuilder, "..", lastStyle, true); |
| 375 | senderBuilder.append(" "); |
| 376 | } |
| 377 | lastFragment = null; |
| 378 | } else if (SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) { |
| 379 | i++; |
| 380 | } else if (SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) { |
| 381 | i++; |
| 382 | } else if (SENDER_LIST_TOKEN_SENDING.equals(fragment0)) { |
| 383 | } else if (SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) { |
| 384 | } else { |
| 385 | final String unreadString = fragment0; |
| 386 | final String priorityString = fragments[i++]; |
| 387 | String nameString = fragments[i++]; |
| 388 | if (nameString.length() == 0) { |
| 389 | nameString = meString.toString(); |
| 390 | } else { |
| 391 | nameString = Html.fromHtml(nameString).toString(); |
| 392 | } |
| 393 | if (numCharsToRemovePerWord != 0) { |
| 394 | nameString = nameString.substring(0, |
| 395 | Math.max(nameString.length() - numCharsToRemovePerWord, 0)); |
| 396 | } |
| 397 | final boolean unread = unreadStatusIsForced ? forcedUnreadStatus : Integer |
| 398 | .parseInt(unreadString) != 0; |
| 399 | final int priority = Integer.parseInt(priorityString); |
| 400 | if (priority <= maxPriorityToInclude) { |
| 401 | if (lastFragment != null && !lastFragment.equals(nameString)) { |
| 402 | addStyledFragment(senderBuilder, lastFragment.concat(","), lastStyle, |
| 403 | false); |
| 404 | senderBuilder.append(" "); |
| 405 | } |
| 406 | lastFragment = nameString; |
| 407 | lastStyle = unread ? unreadStyle : readStyle; |
| 408 | } else { |
| 409 | if (lastFragment != null) { |
| 410 | addStyledFragment(senderBuilder, lastFragment, lastStyle, false); |
| 411 | // Adjacent spans can cause the TextView in Gmail widget |
| 412 | // confused and leads to weird behavior on scrolling. |
| 413 | // Our workaround here is to separate the spans by |
| 414 | // spaces. |
| 415 | senderBuilder.append(" "); |
| 416 | addStyledFragment(senderBuilder, "..", lastStyle, true); |
| 417 | senderBuilder.append(" "); |
| 418 | } |
| 419 | lastFragment = null; |
| 420 | } |
| 421 | } |
| 422 | } |
| 423 | if (lastFragment != null) { |
| 424 | addStyledFragment(senderBuilder, lastFragment, lastStyle, false); |
| 425 | } |
| 426 | senderBuilder.append(numMessagesFragment); |
| 427 | if (fixedFragmentLength != 0) { |
| 428 | statusBuilder.append(fixedFragment); |
| 429 | } |
| 430 | } |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 431 | |
| 432 | /** |
| 433 | * Adds a fragment with given style to a string builder. |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 434 | * |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 435 | * @param builder the current string builder |
| 436 | * @param fragment the fragment to be added |
| 437 | * @param style the style of the fragment |
| 438 | * @param withSpaces whether to add the whole fragment or to divide it into |
| 439 | * smaller ones |
| 440 | */ |
| 441 | private static void addStyledFragment(SpannableStringBuilder builder, String fragment, |
| 442 | CharacterStyle style, boolean withSpaces) { |
| 443 | if (withSpaces) { |
| 444 | int pos = builder.length(); |
| 445 | builder.append(fragment); |
| 446 | builder.setSpan(CharacterStyle.wrap(style), pos, builder.length(), |
| 447 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| 448 | } else { |
| 449 | int start = 0; |
| 450 | while (true) { |
| 451 | int pos = fragment.substring(start).indexOf(' '); |
| 452 | if (pos == -1) { |
| 453 | addStyledFragment(builder, fragment.substring(start), style, true); |
| 454 | break; |
| 455 | } else { |
| 456 | pos += start; |
| 457 | if (start < pos) { |
| 458 | addStyledFragment(builder, fragment.substring(start, pos), style, true); |
| 459 | builder.append(' '); |
| 460 | } |
| 461 | start = pos + 1; |
| 462 | if (start >= fragment.length()) { |
| 463 | break; |
| 464 | } |
| 465 | } |
| 466 | } |
| 467 | } |
| 468 | } |
| 469 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 470 | /** |
| 471 | * Returns a boolean indicating whether the table UI should be shown. |
| 472 | */ |
| 473 | public static boolean useTabletUI(Context context) { |
| 474 | return context.getResources().getInteger(R.integer.use_tablet_ui) != 0; |
| 475 | } |
Mindy Pereira | 4ebb916 | 2012-01-03 11:06:19 -0800 | [diff] [blame] | 476 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 477 | /** |
| 478 | * Perform a simulated measure pass on the given child view, assuming the |
| 479 | * child has a ViewGroup parent and that it should be laid out within that |
| 480 | * parent with a matching width but variable height. Code largely lifted |
| 481 | * from AnimatedAdapter.measureChildHeight(). |
| 482 | * |
| 483 | * @param child a child view that has already been placed within its parent |
| 484 | * ViewGroup |
| 485 | * @param parent the parent ViewGroup of child |
| 486 | * @return measured height of the child in px |
| 487 | */ |
| 488 | public static int measureViewHeight(View child, ViewGroup parent) { |
| 489 | int parentWSpec = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY); |
| 490 | int wSpec = ViewGroup.getChildMeasureSpec(parentWSpec, |
| 491 | parent.getPaddingLeft() + parent.getPaddingRight(), |
| 492 | ViewGroup.LayoutParams.MATCH_PARENT); |
| 493 | int hSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); |
| 494 | child.measure(wSpec, hSpec); |
| 495 | return child.getMeasuredHeight(); |
| 496 | } |
Mindy Pereira | 326c660 | 2012-01-04 15:32:42 -0800 | [diff] [blame] | 497 | |
Mindy Pereira | 46ce0b1 | 2012-01-05 10:32:15 -0800 | [diff] [blame] | 498 | /** |
| 499 | * Encode the string in HTML. |
| 500 | * |
| 501 | * @param removeEmptyDoubleQuotes If true, also remove any occurrence of "" |
| 502 | * found in the string |
| 503 | */ |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 504 | public static Object cleanUpString(String string, boolean removeEmptyDoubleQuotes) { |
| 505 | return !TextUtils.isEmpty(string) ? TextUtils.htmlEncode(removeEmptyDoubleQuotes ? string |
| 506 | .replace("\"\"", "") : string) : ""; |
| 507 | } |
Mindy Pereira | 46ce0b1 | 2012-01-05 10:32:15 -0800 | [diff] [blame] | 508 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 509 | /** |
| 510 | * Returns comma seperated strings as an array. |
| 511 | */ |
| 512 | public static String[] splitCommaSeparatedString(String str) { |
| 513 | return TextUtils.isEmpty(str) ? new String[0] : TextUtils.split(str, ","); |
| 514 | } |
Mindy Pereira | 4a27ea9 | 2012-01-05 15:55:25 -0800 | [diff] [blame] | 515 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 516 | /** |
| 517 | * Get the correct display string for the unread count of a folder. |
| 518 | */ |
| 519 | public static String getUnreadCountString(Context context, int unreadCount) { |
| 520 | String unreadCountString; |
| 521 | Resources resources = context.getResources(); |
| 522 | if (sMaxUnreadCount == -1) { |
| 523 | sMaxUnreadCount = resources.getInteger(R.integer.maxUnreadCount); |
| 524 | } |
| 525 | if (unreadCount > sMaxUnreadCount) { |
| 526 | if (sUnreadText == null) { |
| 527 | sUnreadText = resources.getString(R.string.widget_large_unread_count); |
| 528 | } |
| 529 | unreadCountString = String.format(sUnreadText, sMaxUnreadCount); |
| 530 | } else if (unreadCount <= 0) { |
| 531 | unreadCountString = ""; |
| 532 | } else { |
| 533 | unreadCountString = String.valueOf(unreadCount); |
| 534 | } |
| 535 | return unreadCountString; |
| 536 | } |
Mindy Pereira | 28beb84 | 2012-02-23 09:27:07 -0800 | [diff] [blame] | 537 | |
| 538 | /** |
| 539 | * Get text matching the last sync status. |
| 540 | */ |
| 541 | public static CharSequence getSyncStatusText(Context context, int status) { |
| 542 | String[] errors = context.getResources().getStringArray(R.array.sync_status); |
| 543 | if (status >= errors.length) { |
| 544 | return ""; |
| 545 | } |
| 546 | return errors[status]; |
| 547 | } |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 548 | |
| 549 | /** |
| 550 | * @return an intent which, if launched, will display the corresponding conversation |
| 551 | */ |
| 552 | public static Intent createViewConversationIntent(Context context, |
| 553 | Account account, Folder folder, long conversationId) { |
| 554 | final Intent intent = new Intent(Intent.ACTION_VIEW); |
| 555 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| 556 | Uri.Builder builder = new Uri.Builder(); |
| 557 | builder.scheme("content"); |
| 558 | builder.authority(UIProvider.AUTHORITY); |
| 559 | builder.appendEncodedPath("account/" + account); |
| 560 | final String intentLabel = folder.name != null ? folder.name |
| 561 | : account.getAccountInbox().name; |
| 562 | builder.appendPath("label"); |
| 563 | builder.appendPath(intentLabel); |
| 564 | |
| 565 | if (conversationId != UIProvider.INVALID_CONVERSATION_ID) { |
| 566 | builder.appendEncodedPath("conversationId/" + conversationId); |
| 567 | } |
| 568 | intent.setDataAndType(builder.build(), "application/" + UIProvider.AUTHORITY); |
| 569 | |
| 570 | return intent; |
| 571 | } |
Mindy Pereira | 7b56a61 | 2011-12-14 12:32:28 -0800 | [diff] [blame] | 572 | } |