blob: 2fe83e00325333b2649ea428326eb264360364ac [file] [log] [blame]
Mindy Pereira7b56a612011-12-14 12:32:28 -08001/**
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 Pereira2c47a112012-02-16 16:08:54 -080016
Andy Huang30e2c242012-01-06 18:14:30 -080017package com.android.mail.utils;
Mindy Pereira7b56a612011-12-14 12:32:28 -080018
Mindy Pereira6f92de62011-12-19 11:31:48 -080019import android.content.Context;
Mindy Pereira8a8c50d2012-02-23 11:09:03 -080020import android.content.Intent;
Mindy Pereira6f92de62011-12-19 11:31:48 -080021import android.content.res.Resources;
22import android.graphics.Typeface;
Mindy Pereira8a8c50d2012-02-23 11:09:03 -080023import android.net.Uri;
Mindy Pereira3e0426c2011-12-20 11:12:19 -080024import android.text.Html;
25import android.text.Spannable;
Mindy Pereira6f92de62011-12-19 11:31:48 -080026import android.text.SpannableString;
27import android.text.SpannableStringBuilder;
Mindy Pereira3e0426c2011-12-20 11:12:19 -080028import android.text.Spanned;
29import android.text.TextUtils;
30import android.text.TextUtils.SimpleStringSplitter;
Mindy Pereira6f92de62011-12-19 11:31:48 -080031import android.text.style.CharacterStyle;
32import android.text.style.ForegroundColorSpan;
33import android.text.style.StyleSpan;
Mindy Pereira326c6602012-01-04 15:32:42 -080034import android.view.View;
35import android.view.ViewGroup;
36import android.view.View.MeasureSpec;
Mindy Pereira8b99ba42011-12-16 09:57:18 -080037import android.webkit.WebSettings;
38import android.webkit.WebView;
39
Andy Huang30e2c242012-01-06 18:14:30 -080040import com.android.mail.R;
Mindy Pereira8a8c50d2012-02-23 11:09:03 -080041import com.android.mail.providers.Account;
42import com.android.mail.providers.Folder;
43import com.android.mail.providers.UIProvider;
Mindy Pereira3e0426c2011-12-20 11:12:19 -080044import com.google.common.collect.Maps;
45
46import java.util.Map;
Mindy Pereira7b56a612011-12-14 12:32:28 -080047
Mindy Pereira6f92de62011-12-19 11:31:48 -080048public 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 Pereira3e0426c2011-12-20 11:12:19 -080053 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 Pereira8b99ba42011-12-16 09:57:18 -080064
Mindy Pereira6349a042012-01-04 11:25:01 -080065 public static final String EXTRA_ACCOUNT = "account";
Mindy Pereira8a8c50d2012-02-23 11:09:03 -080066 /*
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 Pereira6349a042012-01-04 11:25:01 -080074
Mindy Pereira2c47a112012-02-16 16:08:54 -080075 /**
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 Pereira8b99ba42011-12-16 09:57:18 -080081 WebSettings webSettings = webView.getSettings();
82 webSettings.setSavePassword(false);
83 webSettings.setSaveFormData(false);
84 webSettings.setJavaScriptEnabled(true);
85 webSettings.setSupportZoom(false);
Mindy Pereira2c47a112012-02-16 16:08:54 -080086 }
Mindy Pereira6f92de62011-12-19 11:31:48 -080087
Mindy Pereira2c47a112012-02-16 16:08:54 -080088 /**
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 Pereira6f92de62011-12-19 11:31:48 -080098
Mindy Pereira2c47a112012-02-16 16:08:54 -080099 /**
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 Pereira6f92de62011-12-19 11:31:48 -0800109
Mindy Pereira2c47a112012-02-16 16:08:54 -0800110 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 Pereira6f92de62011-12-19 11:31:48 -0800125
Mindy Pereira2c47a112012-02-16 16:08:54 -0800126 /**
Mindy Pereira4ebb9162012-01-03 11:06:19 -0800127 * 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 Pereira2c47a112012-02-16 16:08:54 -0800134 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 Pereira4ebb9162012-01-03 11:06:19 -0800144
Mindy Pereira2c47a112012-02-16 16:08:54 -0800145 // 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 Pereira6f92de62011-12-19 11:31:48 -0800154
Mindy Pereira2c47a112012-02-16 16:08:54 -0800155 private static int sMaxUnreadCount = -1;
156 private static String sUnreadText;
Mindy Pereira6f92de62011-12-19 11:31:48 -0800157
Mindy Pereira2c47a112012-02-16 16:08:54 -0800158 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 Pereira6f92de62011-12-19 11:31:48 -0800166
Mindy Pereira2c47a112012-02-16 16:08:54 -0800167 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 Pereira6f92de62011-12-19 11:31:48 -0800176
Mindy Pereira2c47a112012-02-16 16:08:54 -0800177 getSenderSnippet(senderInstructions, senderBuilder, statusBuilder, maxChars,
178 sUnreadStyleSpan, sReadStyleSpan, sDraftsStyleSpan, sMeString,
179 sDraftSingularString, sDraftPluralString, sSendingString, sSendFailedString,
180 forceAllUnread, forceAllRead, allowDraft);
181 }
182
183 /**
Mindy Pereira3e0426c2011-12-20 11:12:19 -0800184 * 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 Pereira2c47a112012-02-16 16:08:54 -0800217 *
Mindy Pereira3e0426c2011-12-20 11:12:19 -0800218 * @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 Pereira2c47a112012-02-16 16:08:54 -0800233 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 Pereira3e0426c2011-12-20 11:12:19 -0800243
Mindy Pereira2c47a112012-02-16 16:08:54 -0800244 // 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 Pereira3e0426c2011-12-20 11:12:19 -0800249
Mindy Pereira2c47a112012-02-16 16:08:54 -0800250 int maxFoundPriority = Integer.MIN_VALUE;
251 int numMessages = 0;
252 int numDrafts = 0;
253 CharSequence draftsFragment = "";
254 CharSequence sendingFragment = "";
255 CharSequence sendFailedFragment = "";
Mindy Pereira3e0426c2011-12-20 11:12:19 -0800256
Mindy Pereira2c47a112012-02-16 16:08:54 -0800257 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 Pereira3e0426c2011-12-20 11:12:19 -0800270
Mindy Pereira2c47a112012-02-16 16:08:54 -0800271 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 Pereira3e0426c2011-12-20 11:12:19 -0800303
Mindy Pereira2c47a112012-02-16 16:08:54 -0800304 // 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 Pereira3e0426c2011-12-20 11:12:19 -0800333
Mindy Pereira2c47a112012-02-16 16:08:54 -0800334 if (fixedFragment != null) {
335 fixedFragmentLength = fixedFragment.length();
336 }
337 maxChars -= fixedFragmentLength;
Mindy Pereira3e0426c2011-12-20 11:12:19 -0800338
Mindy Pereira2c47a112012-02-16 16:08:54 -0800339 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 Pereira3e0426c2011-12-20 11:12:19 -0800358
Mindy Pereira2c47a112012-02-16 16:08:54 -0800359 int numCharsToRemovePerWord = 0;
360 if (numCharsUsed > maxChars) {
361 numCharsToRemovePerWord = (numCharsUsed - maxChars) / numSendersUsed;
362 }
Mindy Pereira3e0426c2011-12-20 11:12:19 -0800363
Mindy Pereira2c47a112012-02-16 16:08:54 -0800364 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 Pereira3e0426c2011-12-20 11:12:19 -0800431
432 /**
433 * Adds a fragment with given style to a string builder.
Mindy Pereira2c47a112012-02-16 16:08:54 -0800434 *
Mindy Pereira3e0426c2011-12-20 11:12:19 -0800435 * @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 Pereira2c47a112012-02-16 16:08:54 -0800470 /**
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 Pereira4ebb9162012-01-03 11:06:19 -0800476
Mindy Pereira2c47a112012-02-16 16:08:54 -0800477 /**
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 Pereira326c6602012-01-04 15:32:42 -0800497
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800498 /**
499 * Encode the string in HTML.
500 *
501 * @param removeEmptyDoubleQuotes If true, also remove any occurrence of ""
502 * found in the string
503 */
Mindy Pereira2c47a112012-02-16 16:08:54 -0800504 public static Object cleanUpString(String string, boolean removeEmptyDoubleQuotes) {
505 return !TextUtils.isEmpty(string) ? TextUtils.htmlEncode(removeEmptyDoubleQuotes ? string
506 .replace("\"\"", "") : string) : "";
507 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800508
Mindy Pereira2c47a112012-02-16 16:08:54 -0800509 /**
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 Pereira4a27ea92012-01-05 15:55:25 -0800515
Mindy Pereira2c47a112012-02-16 16:08:54 -0800516 /**
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 Pereira28beb842012-02-23 09:27:07 -0800537
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 Pereira8a8c50d2012-02-23 11:09:03 -0800548
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 Pereira7b56a612011-12-14 12:32:28 -0800572}