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 | |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 19 | import com.google.common.collect.Maps; |
| 20 | |
Mindy Pereira | 68f2e22 | 2012-03-07 10:36:54 -0800 | [diff] [blame] | 21 | import android.app.SearchManager; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 22 | import android.content.Context; |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 23 | import android.content.Intent; |
Paul Westbrook | 94e440d | 2012-02-24 11:03:47 -0800 | [diff] [blame] | 24 | import android.content.pm.PackageManager.NameNotFoundException; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 25 | import android.content.res.Resources; |
Mindy Pereira | b5080d5 | 2012-03-09 11:26:44 -0800 | [diff] [blame^] | 26 | import android.graphics.Color; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 27 | import android.graphics.Typeface; |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 28 | import android.net.Uri; |
Paul Westbrook | 94e440d | 2012-02-24 11:03:47 -0800 | [diff] [blame] | 29 | import android.provider.Browser; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 30 | import android.text.Html; |
| 31 | import android.text.Spannable; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 32 | import android.text.SpannableString; |
| 33 | import android.text.SpannableStringBuilder; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 34 | import android.text.Spanned; |
| 35 | import android.text.TextUtils; |
| 36 | import android.text.TextUtils.SimpleStringSplitter; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 37 | import android.text.style.CharacterStyle; |
| 38 | import android.text.style.ForegroundColorSpan; |
| 39 | import android.text.style.StyleSpan; |
Mindy Pereira | 326c660 | 2012-01-04 15:32:42 -0800 | [diff] [blame] | 40 | import android.view.View; |
Mindy Pereira | 326c660 | 2012-01-04 15:32:42 -0800 | [diff] [blame] | 41 | import android.view.View.MeasureSpec; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 42 | import android.view.ViewGroup; |
Mindy Pereira | 8b99ba4 | 2011-12-16 09:57:18 -0800 | [diff] [blame] | 43 | import android.webkit.WebSettings; |
| 44 | import android.webkit.WebView; |
| 45 | |
Andy Huang | 30e2c24 | 2012-01-06 18:14:30 -0800 | [diff] [blame] | 46 | import com.android.mail.R; |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 47 | import com.android.mail.providers.Account; |
Mindy Pereira | 9ae8ce0 | 2012-02-28 09:28:15 -0800 | [diff] [blame] | 48 | import com.android.mail.providers.Conversation; |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 49 | import com.android.mail.providers.Folder; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 50 | |
Paul Westbrook | 94e440d | 2012-02-24 11:03:47 -0800 | [diff] [blame] | 51 | import java.util.Locale; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 52 | import java.util.Map; |
Mindy Pereira | 7b56a61 | 2011-12-14 12:32:28 -0800 | [diff] [blame] | 53 | |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 54 | public class Utils { |
| 55 | /** |
| 56 | * longest extension we recognize is 4 characters (e.g. "html", "docx") |
| 57 | */ |
| 58 | private static final int FILE_EXTENSION_MAX_CHARS = 4; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 59 | private static final Map<Integer, Integer> sPriorityToLength = Maps.newHashMap(); |
| 60 | public static final String SENDER_LIST_TOKEN_ELIDED = "e"; |
| 61 | public static final String SENDER_LIST_TOKEN_NUM_MESSAGES = "n"; |
| 62 | public static final String SENDER_LIST_TOKEN_NUM_DRAFTS = "d"; |
| 63 | public static final String SENDER_LIST_TOKEN_LITERAL = "l"; |
| 64 | public static final String SENDER_LIST_TOKEN_SENDING = "s"; |
| 65 | public static final String SENDER_LIST_TOKEN_SEND_FAILED = "f"; |
| 66 | public static final Character SENDER_LIST_SEPARATOR = '\n'; |
| 67 | public static final SimpleStringSplitter sSenderListSplitter = new SimpleStringSplitter( |
| 68 | SENDER_LIST_SEPARATOR); |
| 69 | public static String[] sSenderFragments = new String[8]; |
Mindy Pereira | 8b99ba4 | 2011-12-16 09:57:18 -0800 | [diff] [blame] | 70 | |
Mindy Pereira | 6349a04 | 2012-01-04 11:25:01 -0800 | [diff] [blame] | 71 | public static final String EXTRA_ACCOUNT = "account"; |
Mindy Pereira | 7418e4b | 2012-02-28 11:32:14 -0800 | [diff] [blame] | 72 | public static final String EXTRA_COMPOSE_URI = "composeUri"; |
Mindy Pereira | 963cded | 2012-02-28 15:25:21 -0800 | [diff] [blame] | 73 | public static final String EXTRA_CONVERSATION = "conversationUri"; |
| 74 | public static final String EXTRA_FOLDER = "folder"; |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 75 | /* |
| 76 | * Notifies that changes happened. Certain UI components, e.g., widgets, can |
| 77 | * register for this {@link Intent} and update accordingly. However, this |
| 78 | * can be very broad and is NOT the preferred way of getting notification. |
| 79 | */ |
| 80 | // TODO: UI Provider has this notification URI? |
Paul Westbrook | 94e440d | 2012-02-24 11:03:47 -0800 | [diff] [blame] | 81 | public static final String ACTION_NOTIFY_DATASET_CHANGED = |
| 82 | "com.android.mail.ACTION_NOTIFY_DATASET_CHANGED"; |
| 83 | |
| 84 | /** Parameter keys for context-aware help. */ |
| 85 | private static final String SMART_HELP_LINK_PARAMETER_NAME = "p"; |
| 86 | |
| 87 | private static final String SMART_LINK_APP_VERSION = "version"; |
| 88 | private static String sVersionCode = null; |
| 89 | |
| 90 | private static final String LOG_TAG = new LogUtils().getLogTag(); |
Mindy Pereira | 6349a04 | 2012-01-04 11:25:01 -0800 | [diff] [blame] | 91 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 92 | /** |
| 93 | * Sets WebView in a restricted mode suitable for email use. |
| 94 | * |
| 95 | * @param webView The WebView to restrict |
| 96 | */ |
| 97 | public static void restrictWebView(WebView webView) { |
Mindy Pereira | 8b99ba4 | 2011-12-16 09:57:18 -0800 | [diff] [blame] | 98 | WebSettings webSettings = webView.getSettings(); |
| 99 | webSettings.setSavePassword(false); |
| 100 | webSettings.setSaveFormData(false); |
| 101 | webSettings.setJavaScriptEnabled(true); |
| 102 | webSettings.setSupportZoom(false); |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 103 | } |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 104 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 105 | /** |
| 106 | * Format a plural string. |
| 107 | * |
| 108 | * @param resource The identity of the resource, which must be a R.plurals |
| 109 | * @param count The number of items. |
| 110 | */ |
| 111 | public static String formatPlural(Context context, int resource, int count) { |
| 112 | CharSequence formatString = context.getResources().getQuantityText(resource, count); |
| 113 | return String.format(formatString.toString(), count); |
| 114 | } |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 115 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 116 | /** |
| 117 | * @return an ellipsized String that's at most maxCharacters long. If the |
| 118 | * text passed is longer, it will be abbreviated. If it contains a |
| 119 | * suffix, the ellipses will be inserted in the middle and the |
| 120 | * suffix will be preserved. |
| 121 | */ |
| 122 | public static String ellipsize(String text, int maxCharacters) { |
| 123 | int length = text.length(); |
| 124 | if (length < maxCharacters) |
| 125 | return text; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 126 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 127 | int realMax = Math.min(maxCharacters, length); |
| 128 | // Preserve the suffix if any |
| 129 | int index = text.lastIndexOf("."); |
| 130 | String extension = "\u2026"; // "..."; |
| 131 | if (index >= 0) { |
| 132 | // Limit the suffix to dot + four characters |
| 133 | if (length - index <= FILE_EXTENSION_MAX_CHARS + 1) { |
| 134 | extension = extension + text.substring(index + 1); |
| 135 | } |
| 136 | } |
| 137 | realMax -= extension.length(); |
| 138 | if (realMax < 0) |
| 139 | realMax = 0; |
| 140 | return text.substring(0, realMax) + extension; |
| 141 | } |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 142 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 143 | /** |
Mindy Pereira | 4ebb916 | 2012-01-03 11:06:19 -0800 | [diff] [blame] | 144 | * Ensures that the given string starts and ends with the double quote |
| 145 | * character. The string is not modified in any way except to add the double |
| 146 | * quote character to start and end if it's not already there. sample -> |
| 147 | * "sample" "sample" -> "sample" ""sample"" -> "sample" |
| 148 | * "sample"" -> "sample" sa"mp"le -> "sa"mp"le" "sa"mp"le" -> "sa"mp"le" |
| 149 | * (empty string) -> "" " -> "" |
| 150 | */ |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 151 | public static String ensureQuotedString(String s) { |
| 152 | if (s == null) { |
| 153 | return null; |
| 154 | } |
| 155 | if (!s.matches("^\".*\"$")) { |
| 156 | return "\"" + s + "\""; |
| 157 | } else { |
| 158 | return s; |
| 159 | } |
| 160 | } |
Mindy Pereira | 4ebb916 | 2012-01-03 11:06:19 -0800 | [diff] [blame] | 161 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 162 | // TODO: Move this to the UI Provider. |
| 163 | private static CharacterStyle sUnreadStyleSpan = null; |
| 164 | private static CharacterStyle sReadStyleSpan; |
| 165 | private static CharacterStyle sDraftsStyleSpan; |
| 166 | private static CharSequence sMeString; |
| 167 | private static CharSequence sDraftSingularString; |
| 168 | private static CharSequence sDraftPluralString; |
| 169 | private static CharSequence sSendingString; |
| 170 | private static CharSequence sSendFailedString; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 171 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 172 | private static int sMaxUnreadCount = -1; |
| 173 | private static String sUnreadText; |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 174 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 175 | public static void getStyledSenderSnippet(Context context, String senderInstructions, |
| 176 | SpannableStringBuilder senderBuilder, SpannableStringBuilder statusBuilder, |
| 177 | int maxChars, boolean forceAllUnread, boolean forceAllRead, boolean allowDraft) { |
| 178 | Resources res = context.getResources(); |
| 179 | if (sUnreadStyleSpan == null) { |
| 180 | sUnreadStyleSpan = new StyleSpan(Typeface.BOLD); |
| 181 | sReadStyleSpan = new StyleSpan(Typeface.NORMAL); |
| 182 | sDraftsStyleSpan = new ForegroundColorSpan(res.getColor(R.color.drafts)); |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 183 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 184 | sMeString = context.getText(R.string.me); |
| 185 | sDraftSingularString = res.getQuantityText(R.plurals.draft, 1); |
| 186 | sDraftPluralString = res.getQuantityText(R.plurals.draft, 2); |
| 187 | SpannableString sendingString = new SpannableString(context.getText(R.string.sending)); |
| 188 | sendingString.setSpan(CharacterStyle.wrap(sDraftsStyleSpan), 0, sendingString.length(), |
| 189 | 0); |
| 190 | sSendingString = sendingString; |
| 191 | sSendFailedString = context.getText(R.string.send_failed); |
| 192 | } |
Mindy Pereira | 6f92de6 | 2011-12-19 11:31:48 -0800 | [diff] [blame] | 193 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 194 | getSenderSnippet(senderInstructions, senderBuilder, statusBuilder, maxChars, |
| 195 | sUnreadStyleSpan, sReadStyleSpan, sDraftsStyleSpan, sMeString, |
| 196 | sDraftSingularString, sDraftPluralString, sSendingString, sSendFailedString, |
| 197 | forceAllUnread, forceAllRead, allowDraft); |
| 198 | } |
| 199 | |
| 200 | /** |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 201 | * Uses sender instructions to build a formatted string. |
| 202 | * <p> |
| 203 | * Sender list instructions contain compact information about the sender |
| 204 | * list. Most work that can be done without knowing how much room will be |
| 205 | * availble for the sender list is done when creating the instructions. |
| 206 | * <p> |
| 207 | * The instructions string consists of tokens separated by |
| 208 | * SENDER_LIST_SEPARATOR. Here are the tokens, one per line: |
| 209 | * <ul> |
| 210 | * <li><tt>n</tt></li> |
| 211 | * <li><em>int</em>, the number of non-draft messages in the conversation</li> |
| 212 | * <li><tt>d</tt</li> |
| 213 | * <li><em>int</em>, the number of drafts in the conversation</li> |
| 214 | * <li><tt>l</tt></li> |
| 215 | * <li><em>literal html to be included in the output</em></li> |
| 216 | * <li><tt>s</tt> indicates that the message is sending (in the outbox |
| 217 | * without errors)</li> |
| 218 | * <li><tt>f</tt> indicates that the message failed to send (in the outbox |
| 219 | * with errors)</li> |
| 220 | * <li><em>for each message</em> |
| 221 | * <ul> |
| 222 | * <li><em>int</em>, 0 for read, 1 for unread</li> |
| 223 | * <li><em>int</em>, the priority of the message. Zero is the most important |
| 224 | * </li> |
| 225 | * <li><em>text</em>, the sender text or blank for messages from 'me'</li> |
| 226 | * </ul> |
| 227 | * </li> |
| 228 | * <li><tt>e</tt> to indicate that one or more messages have been elided</li> |
| 229 | * <p> |
| 230 | * The instructions indicate how many messages and drafts are in the |
| 231 | * conversation and then describe the most important messages in order, |
| 232 | * indicating the priority of each message and whether the message is |
| 233 | * unread. |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 234 | * |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 235 | * @param instructions instructions as described above |
| 236 | * @param senderBuilder the SpannableStringBuilder to append to for sender |
| 237 | * information |
| 238 | * @param statusBuilder the SpannableStringBuilder to append to for status |
| 239 | * @param maxChars the number of characters available to display the text |
| 240 | * @param unreadStyle the CharacterStyle for unread messages, or null |
| 241 | * @param draftsStyle the CharacterStyle for draft messages, or null |
| 242 | * @param sendingString the string to use when there are messages scheduled |
| 243 | * to be sent |
| 244 | * @param sendFailedString the string to use when there are messages that |
| 245 | * mailed to send |
| 246 | * @param meString the string to use for messages sent by this user |
| 247 | * @param draftString the string to use for "Draft" |
| 248 | * @param draftPluralString the string to use for "Drafts" |
| 249 | */ |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 250 | public static synchronized void getSenderSnippet(String instructions, |
| 251 | SpannableStringBuilder senderBuilder, SpannableStringBuilder statusBuilder, |
| 252 | int maxChars, CharacterStyle unreadStyle, CharacterStyle readStyle, |
| 253 | CharacterStyle draftsStyle, CharSequence meString, CharSequence draftString, |
| 254 | CharSequence draftPluralString, CharSequence sendingString, |
| 255 | CharSequence sendFailedString, boolean forceAllUnread, boolean forceAllRead, |
| 256 | boolean allowDraft) { |
| 257 | assert !(forceAllUnread && forceAllRead); |
| 258 | boolean unreadStatusIsForced = forceAllUnread || forceAllRead; |
| 259 | boolean forcedUnreadStatus = forceAllUnread; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 260 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 261 | // Measure each fragment. It's ok to iterate over the entire set of |
| 262 | // fragments because it is |
| 263 | // never a long list, even if there are many senders. |
| 264 | final Map<Integer, Integer> priorityToLength = sPriorityToLength; |
| 265 | priorityToLength.clear(); |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 266 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 267 | int maxFoundPriority = Integer.MIN_VALUE; |
| 268 | int numMessages = 0; |
| 269 | int numDrafts = 0; |
| 270 | CharSequence draftsFragment = ""; |
| 271 | CharSequence sendingFragment = ""; |
| 272 | CharSequence sendFailedFragment = ""; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 273 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 274 | sSenderListSplitter.setString(instructions); |
| 275 | int numFragments = 0; |
| 276 | String[] fragments = sSenderFragments; |
| 277 | int currentSize = fragments.length; |
| 278 | while (sSenderListSplitter.hasNext()) { |
| 279 | fragments[numFragments++] = sSenderListSplitter.next(); |
| 280 | if (numFragments == currentSize) { |
| 281 | sSenderFragments = new String[2 * currentSize]; |
| 282 | System.arraycopy(fragments, 0, sSenderFragments, 0, currentSize); |
| 283 | currentSize *= 2; |
| 284 | fragments = sSenderFragments; |
| 285 | } |
| 286 | } |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 287 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 288 | for (int i = 0; i < numFragments;) { |
| 289 | String fragment0 = fragments[i++]; |
| 290 | if ("".equals(fragment0)) { |
| 291 | // This should be the final fragment. |
| 292 | } else if (SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) { |
| 293 | // ignore |
| 294 | } else if (SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) { |
| 295 | numMessages = Integer.valueOf(fragments[i++]); |
| 296 | } else if (SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) { |
| 297 | String numDraftsString = fragments[i++]; |
| 298 | numDrafts = Integer.parseInt(numDraftsString); |
| 299 | draftsFragment = numDrafts == 1 ? draftString : draftPluralString + " (" |
| 300 | + numDraftsString + ")"; |
| 301 | } else if (SENDER_LIST_TOKEN_LITERAL.equals(fragment0)) { |
| 302 | senderBuilder.append(Html.fromHtml(fragments[i++])); |
| 303 | return; |
| 304 | } else if (SENDER_LIST_TOKEN_SENDING.equals(fragment0)) { |
| 305 | sendingFragment = sendingString; |
| 306 | } else if (SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) { |
| 307 | sendFailedFragment = sendFailedString; |
| 308 | } else { |
| 309 | String priorityString = fragments[i++]; |
| 310 | CharSequence nameString = fragments[i++]; |
| 311 | if (nameString.length() == 0) |
| 312 | nameString = meString; |
| 313 | int priority = Integer.parseInt(priorityString); |
| 314 | priorityToLength.put(priority, nameString.length()); |
| 315 | maxFoundPriority = Math.max(maxFoundPriority, priority); |
| 316 | } |
| 317 | } |
| 318 | String numMessagesFragment = (numMessages != 0) ? " \u00A0" |
| 319 | + Integer.toString(numMessages + numDrafts) : ""; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 320 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 321 | // Don't allocate fixedFragment unless we need it |
| 322 | SpannableStringBuilder fixedFragment = null; |
| 323 | int fixedFragmentLength = 0; |
| 324 | if (draftsFragment.length() != 0 && allowDraft) { |
| 325 | if (fixedFragment == null) { |
| 326 | fixedFragment = new SpannableStringBuilder(); |
| 327 | } |
| 328 | fixedFragment.append(draftsFragment); |
| 329 | if (draftsStyle != null) { |
| 330 | fixedFragment.setSpan(CharacterStyle.wrap(draftsStyle), 0, fixedFragment.length(), |
| 331 | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); |
| 332 | } |
| 333 | } |
| 334 | if (sendingFragment.length() != 0) { |
| 335 | if (fixedFragment == null) { |
| 336 | fixedFragment = new SpannableStringBuilder(); |
| 337 | } |
| 338 | if (fixedFragment.length() != 0) |
| 339 | fixedFragment.append(", "); |
| 340 | fixedFragment.append(sendingFragment); |
| 341 | } |
| 342 | if (sendFailedFragment.length() != 0) { |
| 343 | if (fixedFragment == null) { |
| 344 | fixedFragment = new SpannableStringBuilder(); |
| 345 | } |
| 346 | if (fixedFragment.length() != 0) |
| 347 | fixedFragment.append(", "); |
| 348 | fixedFragment.append(sendFailedFragment); |
| 349 | } |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 350 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 351 | if (fixedFragment != null) { |
| 352 | fixedFragmentLength = fixedFragment.length(); |
| 353 | } |
| 354 | maxChars -= fixedFragmentLength; |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 355 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 356 | int maxPriorityToInclude = -1; // inclusive |
| 357 | int numCharsUsed = numMessagesFragment.length(); |
| 358 | int numSendersUsed = 0; |
| 359 | while (maxPriorityToInclude < maxFoundPriority) { |
| 360 | if (priorityToLength.containsKey(maxPriorityToInclude + 1)) { |
| 361 | int length = numCharsUsed + priorityToLength.get(maxPriorityToInclude + 1); |
| 362 | if (numCharsUsed > 0) |
| 363 | length += 2; |
| 364 | // We must show at least two senders if they exist. If we don't |
| 365 | // have space for both |
| 366 | // then we will truncate names. |
| 367 | if (length > maxChars && numSendersUsed >= 2) { |
| 368 | break; |
| 369 | } |
| 370 | numCharsUsed = length; |
| 371 | numSendersUsed++; |
| 372 | } |
| 373 | maxPriorityToInclude++; |
| 374 | } |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 375 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 376 | int numCharsToRemovePerWord = 0; |
| 377 | if (numCharsUsed > maxChars) { |
| 378 | numCharsToRemovePerWord = (numCharsUsed - maxChars) / numSendersUsed; |
| 379 | } |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 380 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 381 | String lastFragment = null; |
| 382 | CharacterStyle lastStyle = null; |
| 383 | for (int i = 0; i < numFragments;) { |
| 384 | String fragment0 = fragments[i++]; |
| 385 | if ("".equals(fragment0)) { |
| 386 | // This should be the final fragment. |
| 387 | } else if (SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) { |
| 388 | if (lastFragment != null) { |
| 389 | addStyledFragment(senderBuilder, lastFragment, lastStyle, false); |
| 390 | senderBuilder.append(" "); |
| 391 | addStyledFragment(senderBuilder, "..", lastStyle, true); |
| 392 | senderBuilder.append(" "); |
| 393 | } |
| 394 | lastFragment = null; |
| 395 | } else if (SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) { |
| 396 | i++; |
| 397 | } else if (SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) { |
| 398 | i++; |
| 399 | } else if (SENDER_LIST_TOKEN_SENDING.equals(fragment0)) { |
| 400 | } else if (SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) { |
| 401 | } else { |
| 402 | final String unreadString = fragment0; |
| 403 | final String priorityString = fragments[i++]; |
| 404 | String nameString = fragments[i++]; |
| 405 | if (nameString.length() == 0) { |
| 406 | nameString = meString.toString(); |
| 407 | } else { |
| 408 | nameString = Html.fromHtml(nameString).toString(); |
| 409 | } |
| 410 | if (numCharsToRemovePerWord != 0) { |
| 411 | nameString = nameString.substring(0, |
| 412 | Math.max(nameString.length() - numCharsToRemovePerWord, 0)); |
| 413 | } |
| 414 | final boolean unread = unreadStatusIsForced ? forcedUnreadStatus : Integer |
| 415 | .parseInt(unreadString) != 0; |
| 416 | final int priority = Integer.parseInt(priorityString); |
| 417 | if (priority <= maxPriorityToInclude) { |
| 418 | if (lastFragment != null && !lastFragment.equals(nameString)) { |
| 419 | addStyledFragment(senderBuilder, lastFragment.concat(","), lastStyle, |
| 420 | false); |
| 421 | senderBuilder.append(" "); |
| 422 | } |
| 423 | lastFragment = nameString; |
| 424 | lastStyle = unread ? unreadStyle : readStyle; |
| 425 | } else { |
| 426 | if (lastFragment != null) { |
| 427 | addStyledFragment(senderBuilder, lastFragment, lastStyle, false); |
| 428 | // Adjacent spans can cause the TextView in Gmail widget |
| 429 | // confused and leads to weird behavior on scrolling. |
| 430 | // Our workaround here is to separate the spans by |
| 431 | // spaces. |
| 432 | senderBuilder.append(" "); |
| 433 | addStyledFragment(senderBuilder, "..", lastStyle, true); |
| 434 | senderBuilder.append(" "); |
| 435 | } |
| 436 | lastFragment = null; |
| 437 | } |
| 438 | } |
| 439 | } |
| 440 | if (lastFragment != null) { |
| 441 | addStyledFragment(senderBuilder, lastFragment, lastStyle, false); |
| 442 | } |
| 443 | senderBuilder.append(numMessagesFragment); |
| 444 | if (fixedFragmentLength != 0) { |
| 445 | statusBuilder.append(fixedFragment); |
| 446 | } |
| 447 | } |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 448 | |
| 449 | /** |
| 450 | * Adds a fragment with given style to a string builder. |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 451 | * |
Mindy Pereira | 3e0426c | 2011-12-20 11:12:19 -0800 | [diff] [blame] | 452 | * @param builder the current string builder |
| 453 | * @param fragment the fragment to be added |
| 454 | * @param style the style of the fragment |
| 455 | * @param withSpaces whether to add the whole fragment or to divide it into |
| 456 | * smaller ones |
| 457 | */ |
| 458 | private static void addStyledFragment(SpannableStringBuilder builder, String fragment, |
| 459 | CharacterStyle style, boolean withSpaces) { |
| 460 | if (withSpaces) { |
| 461 | int pos = builder.length(); |
| 462 | builder.append(fragment); |
| 463 | builder.setSpan(CharacterStyle.wrap(style), pos, builder.length(), |
| 464 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| 465 | } else { |
| 466 | int start = 0; |
| 467 | while (true) { |
| 468 | int pos = fragment.substring(start).indexOf(' '); |
| 469 | if (pos == -1) { |
| 470 | addStyledFragment(builder, fragment.substring(start), style, true); |
| 471 | break; |
| 472 | } else { |
| 473 | pos += start; |
| 474 | if (start < pos) { |
| 475 | addStyledFragment(builder, fragment.substring(start, pos), style, true); |
| 476 | builder.append(' '); |
| 477 | } |
| 478 | start = pos + 1; |
| 479 | if (start >= fragment.length()) { |
| 480 | break; |
| 481 | } |
| 482 | } |
| 483 | } |
| 484 | } |
| 485 | } |
| 486 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 487 | /** |
| 488 | * Returns a boolean indicating whether the table UI should be shown. |
| 489 | */ |
| 490 | public static boolean useTabletUI(Context context) { |
| 491 | return context.getResources().getInteger(R.integer.use_tablet_ui) != 0; |
| 492 | } |
Mindy Pereira | 4ebb916 | 2012-01-03 11:06:19 -0800 | [diff] [blame] | 493 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 494 | /** |
| 495 | * Perform a simulated measure pass on the given child view, assuming the |
| 496 | * child has a ViewGroup parent and that it should be laid out within that |
| 497 | * parent with a matching width but variable height. Code largely lifted |
| 498 | * from AnimatedAdapter.measureChildHeight(). |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 499 | * |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 500 | * @param child a child view that has already been placed within its parent |
| 501 | * ViewGroup |
| 502 | * @param parent the parent ViewGroup of child |
| 503 | * @return measured height of the child in px |
| 504 | */ |
| 505 | public static int measureViewHeight(View child, ViewGroup parent) { |
| 506 | int parentWSpec = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY); |
| 507 | int wSpec = ViewGroup.getChildMeasureSpec(parentWSpec, |
| 508 | parent.getPaddingLeft() + parent.getPaddingRight(), |
| 509 | ViewGroup.LayoutParams.MATCH_PARENT); |
| 510 | int hSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); |
| 511 | child.measure(wSpec, hSpec); |
| 512 | return child.getMeasuredHeight(); |
| 513 | } |
Mindy Pereira | 326c660 | 2012-01-04 15:32:42 -0800 | [diff] [blame] | 514 | |
Mindy Pereira | 46ce0b1 | 2012-01-05 10:32:15 -0800 | [diff] [blame] | 515 | /** |
| 516 | * Encode the string in HTML. |
| 517 | * |
| 518 | * @param removeEmptyDoubleQuotes If true, also remove any occurrence of "" |
| 519 | * found in the string |
| 520 | */ |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 521 | public static Object cleanUpString(String string, boolean removeEmptyDoubleQuotes) { |
| 522 | return !TextUtils.isEmpty(string) ? TextUtils.htmlEncode(removeEmptyDoubleQuotes ? string |
| 523 | .replace("\"\"", "") : string) : ""; |
| 524 | } |
Mindy Pereira | 46ce0b1 | 2012-01-05 10:32:15 -0800 | [diff] [blame] | 525 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 526 | /** |
| 527 | * Returns comma seperated strings as an array. |
| 528 | */ |
| 529 | public static String[] splitCommaSeparatedString(String str) { |
| 530 | return TextUtils.isEmpty(str) ? new String[0] : TextUtils.split(str, ","); |
| 531 | } |
Mindy Pereira | 4a27ea9 | 2012-01-05 15:55:25 -0800 | [diff] [blame] | 532 | |
Mindy Pereira | 2c47a11 | 2012-02-16 16:08:54 -0800 | [diff] [blame] | 533 | /** |
| 534 | * Get the correct display string for the unread count of a folder. |
| 535 | */ |
| 536 | public static String getUnreadCountString(Context context, int unreadCount) { |
| 537 | String unreadCountString; |
| 538 | Resources resources = context.getResources(); |
| 539 | if (sMaxUnreadCount == -1) { |
| 540 | sMaxUnreadCount = resources.getInteger(R.integer.maxUnreadCount); |
| 541 | } |
| 542 | if (unreadCount > sMaxUnreadCount) { |
| 543 | if (sUnreadText == null) { |
| 544 | sUnreadText = resources.getString(R.string.widget_large_unread_count); |
| 545 | } |
| 546 | unreadCountString = String.format(sUnreadText, sMaxUnreadCount); |
| 547 | } else if (unreadCount <= 0) { |
| 548 | unreadCountString = ""; |
| 549 | } else { |
| 550 | unreadCountString = String.valueOf(unreadCount); |
| 551 | } |
| 552 | return unreadCountString; |
| 553 | } |
Mindy Pereira | 28beb84 | 2012-02-23 09:27:07 -0800 | [diff] [blame] | 554 | |
| 555 | /** |
| 556 | * Get text matching the last sync status. |
| 557 | */ |
| 558 | public static CharSequence getSyncStatusText(Context context, int status) { |
| 559 | String[] errors = context.getResources().getStringArray(R.array.sync_status); |
| 560 | if (status >= errors.length) { |
| 561 | return ""; |
| 562 | } |
| 563 | return errors[status]; |
| 564 | } |
Mindy Pereira | 8a8c50d | 2012-02-23 11:09:03 -0800 | [diff] [blame] | 565 | |
| 566 | /** |
Mindy Pereira | 9ae8ce0 | 2012-02-28 09:28:15 -0800 | [diff] [blame] | 567 | * Create an intent to show a conversation. |
| 568 | * @param conversation Conversation to open. |
Mindy Pereira | 161f50d | 2012-02-28 15:47:19 -0800 | [diff] [blame] | 569 | * @param folder |
| 570 | * @param account |
Mindy Pereira | 9ae8ce0 | 2012-02-28 09:28:15 -0800 | [diff] [blame] | 571 | * @return |
| 572 | */ |
Mindy Pereira | 161f50d | 2012-02-28 15:47:19 -0800 | [diff] [blame] | 573 | public static Intent createViewConversationIntent(Conversation conversation, Folder folder, |
| 574 | Account account) { |
Mindy Pereira | 9ae8ce0 | 2012-02-28 09:28:15 -0800 | [diff] [blame] | 575 | final Intent intent = new Intent(Intent.ACTION_VIEW); |
| 576 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
Mindy Pereira | 898cd38 | 2012-03-06 08:42:47 -0800 | [diff] [blame] | 577 | intent.setDataAndType(conversation.uri, account.mimeType); |
Mindy Pereira | 161f50d | 2012-02-28 15:47:19 -0800 | [diff] [blame] | 578 | intent.putExtra(Utils.EXTRA_ACCOUNT, account); |
| 579 | intent.putExtra(Utils.EXTRA_FOLDER, folder); |
Mindy Pereira | 963cded | 2012-02-28 15:25:21 -0800 | [diff] [blame] | 580 | intent.putExtra(Utils.EXTRA_CONVERSATION, conversation); |
Mindy Pereira | 9ae8ce0 | 2012-02-28 09:28:15 -0800 | [diff] [blame] | 581 | return intent; |
| 582 | } |
| 583 | |
| 584 | /** |
| 585 | * Create an intent to open a folder. |
| 586 | * @param folder Folder to open. |
Mindy Pereira | 161f50d | 2012-02-28 15:47:19 -0800 | [diff] [blame] | 587 | * @param account |
Mindy Pereira | 9ae8ce0 | 2012-02-28 09:28:15 -0800 | [diff] [blame] | 588 | * @return |
| 589 | */ |
Mindy Pereira | 161f50d | 2012-02-28 15:47:19 -0800 | [diff] [blame] | 590 | public static Intent createViewFolderIntent(Folder folder, Account account) { |
Mindy Pereira | 9ae8ce0 | 2012-02-28 09:28:15 -0800 | [diff] [blame] | 591 | final Intent intent = new Intent(Intent.ACTION_VIEW); |
| 592 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
Mindy Pereira | 898cd38 | 2012-03-06 08:42:47 -0800 | [diff] [blame] | 593 | intent.setDataAndType(folder.uri, account.mimeType); |
Mindy Pereira | 161f50d | 2012-02-28 15:47:19 -0800 | [diff] [blame] | 594 | intent.putExtra(Utils.EXTRA_ACCOUNT, account); |
Mindy Pereira | 963cded | 2012-02-28 15:25:21 -0800 | [diff] [blame] | 595 | intent.putExtra(Utils.EXTRA_FOLDER, folder); |
Mindy Pereira | 9ae8ce0 | 2012-02-28 09:28:15 -0800 | [diff] [blame] | 596 | return intent; |
| 597 | } |
| 598 | |
| 599 | /** |
Paul Westbrook | 94e440d | 2012-02-24 11:03:47 -0800 | [diff] [blame] | 600 | * Helper method to show context-aware Gmail help. |
| 601 | * |
| 602 | * @param context Context to be used to open the help. |
| 603 | * @param fromWhere Information about the activity the user was in |
| 604 | * when they requested help. |
| 605 | */ |
Mindy Pereira | cfb7f33 | 2012-02-28 10:23:43 -0800 | [diff] [blame] | 606 | public static void showHelp(Context context, Uri accountHelpUrl, String fromWhere) { |
| 607 | final Uri uri = addParamsToUrl(context, accountHelpUrl.toString()); |
Paul Westbrook | 94e440d | 2012-02-24 11:03:47 -0800 | [diff] [blame] | 608 | Uri.Builder builder = uri.buildUpon(); |
| 609 | // Add the activity specific information parameter. |
| 610 | if (fromWhere != null) { |
| 611 | builder = builder.appendQueryParameter(SMART_HELP_LINK_PARAMETER_NAME, fromWhere); |
| 612 | } |
| 613 | |
| 614 | openUrl(context, builder.build()); |
| 615 | } |
| 616 | |
| 617 | /** |
| 618 | * Helper method to open a link in a browser. |
| 619 | * |
| 620 | * @param context Context |
| 621 | * @param uri Uri to open. |
| 622 | */ |
| 623 | private static void openUrl(Context context, Uri uri) { |
| 624 | if(uri == null || TextUtils.isEmpty(uri.toString())) { |
| 625 | LogUtils.wtf(LOG_TAG, "invalid url in Utils.openUrl(): %s", uri); |
| 626 | return; |
| 627 | } |
| 628 | Intent intent = new Intent(Intent.ACTION_VIEW, uri); |
| 629 | intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); |
| 630 | context.startActivity(intent); |
| 631 | } |
| 632 | |
| 633 | |
| 634 | private static Uri addParamsToUrl(Context context, String url) { |
| 635 | url = replaceLocale(url); |
| 636 | Uri.Builder builder = Uri.parse(url).buildUpon(); |
| 637 | final String versionCode = getVersionCode(context); |
| 638 | if (versionCode != null) { |
| 639 | builder = builder.appendQueryParameter(SMART_LINK_APP_VERSION, versionCode); |
| 640 | } |
| 641 | |
| 642 | return builder.build(); |
| 643 | } |
| 644 | |
| 645 | /** |
| 646 | * Replaces the language/country of the device into the given string. The pattern "%locale%" |
| 647 | * will be replaced with the <language_code>_<country_code> value. |
| 648 | * |
| 649 | * @param str the string to replace the language/country within |
| 650 | * |
| 651 | * @return the string with replacement |
| 652 | */ |
| 653 | private static String replaceLocale(String str) { |
| 654 | // Substitute locale if present in string |
| 655 | if (str.contains("%locale%")) { |
| 656 | Locale locale = Locale.getDefault(); |
| 657 | String tmp = locale.getLanguage() + "_" + locale.getCountry().toLowerCase(); |
| 658 | str = str.replace("%locale%", tmp); |
| 659 | } |
| 660 | return str; |
| 661 | } |
| 662 | |
| 663 | /** |
| 664 | * Returns the version code for the package, or null if it cannot be retrieved. |
| 665 | */ |
| 666 | public static String getVersionCode(Context context) { |
| 667 | if (sVersionCode == null) { |
| 668 | try { |
| 669 | sVersionCode = String.valueOf(context.getPackageManager() |
| 670 | .getPackageInfo(context.getPackageName(), 0 /* flags */) |
| 671 | .versionCode); |
| 672 | } catch (NameNotFoundException e) { |
| 673 | LogUtils.e(Utils.LOG_TAG, "Error finding package %s", |
| 674 | context.getApplicationInfo().packageName); |
| 675 | } |
| 676 | } |
| 677 | return sVersionCode; |
| 678 | } |
Mindy Pereira | 1f93668 | 2012-03-02 11:30:33 -0800 | [diff] [blame] | 679 | |
| 680 | /** |
| 681 | * Show the settings screen for the supplied account. |
| 682 | */ |
| 683 | public static void showSettings(Context context, Account account) { |
| 684 | final Intent settingsIntent = new Intent(Intent.ACTION_EDIT, account.settingsIntentUri); |
| 685 | context.startActivity(settingsIntent); |
| 686 | } |
Mindy Pereira | 68f2e22 | 2012-03-07 10:36:54 -0800 | [diff] [blame] | 687 | |
| 688 | /** |
| 689 | * Retrieves the mailbox search query associated with an intent (or null if not available), |
| 690 | * doing proper sanitizing (e.g. trims whitespace). |
| 691 | */ |
| 692 | public static String mailSearchQueryForIntent(Intent intent) { |
| 693 | String query = intent.getStringExtra(SearchManager.QUERY); |
| 694 | return TextUtils.isEmpty(query) ? null : query.trim(); |
| 695 | } |
Andy Huang | 88fc42e | 2012-03-08 15:02:43 -0800 | [diff] [blame] | 696 | |
| 697 | /** |
| 698 | * Split out a filename's extension and return it. |
| 699 | * @param filename a file name |
| 700 | * @return the file extension (max of 5 chars including period, like ".docx"), or null |
| 701 | */ |
| 702 | public static String getFileExtension(String filename) { |
| 703 | String extension = null; |
| 704 | int index = filename.lastIndexOf('.'); |
| 705 | // Limit the suffix to dot + four characters |
| 706 | if (index >= 0 && filename.length() - index <= FILE_EXTENSION_MAX_CHARS + 1) { |
| 707 | extension = filename.substring(index); |
| 708 | } |
| 709 | return extension; |
| 710 | } |
| 711 | |
| 712 | /** |
| 713 | * (copied from {@link Intent#normalizeMimeType(String)} for pre-J) |
| 714 | * |
| 715 | * Normalize a MIME data type. |
| 716 | * |
| 717 | * <p>A normalized MIME type has white-space trimmed, |
| 718 | * content-type parameters removed, and is lower-case. |
| 719 | * This aligns the type with Android best practices for |
| 720 | * intent filtering. |
| 721 | * |
| 722 | * <p>For example, "text/plain; charset=utf-8" becomes "text/plain". |
| 723 | * "text/x-vCard" becomes "text/x-vcard". |
| 724 | * |
| 725 | * <p>All MIME types received from outside Android (such as user input, |
| 726 | * or external sources like Bluetooth, NFC, or the Internet) should |
| 727 | * be normalized before they are used to create an Intent. |
| 728 | * |
| 729 | * @param type MIME data type to normalize |
| 730 | * @return normalized MIME data type, or null if the input was null |
| 731 | * @see {@link #setType} |
| 732 | * @see {@link #setTypeAndNormalize} |
| 733 | */ |
| 734 | public static String normalizeMimeType(String type) { |
| 735 | if (type == null) { |
| 736 | return null; |
| 737 | } |
| 738 | |
| 739 | type = type.trim().toLowerCase(Locale.US); |
| 740 | |
| 741 | final int semicolonIndex = type.indexOf(';'); |
| 742 | if (semicolonIndex != -1) { |
| 743 | type = type.substring(0, semicolonIndex); |
| 744 | } |
| 745 | return type; |
| 746 | } |
| 747 | |
| 748 | /** |
| 749 | * (copied from {@link Uri#normalize()} for pre-J) |
| 750 | * |
| 751 | * Return a normalized representation of this Uri. |
| 752 | * |
| 753 | * <p>A normalized Uri has a lowercase scheme component. |
| 754 | * This aligns the Uri with Android best practices for |
| 755 | * intent filtering. |
| 756 | * |
| 757 | * <p>For example, "HTTP://www.android.com" becomes |
| 758 | * "http://www.android.com" |
| 759 | * |
| 760 | * <p>All URIs received from outside Android (such as user input, |
| 761 | * or external sources like Bluetooth, NFC, or the Internet) should |
| 762 | * be normalized before they are used to create an Intent. |
| 763 | * |
| 764 | * <p class="note">This method does <em>not</em> validate bad URI's, |
| 765 | * or 'fix' poorly formatted URI's - so do not use it for input validation. |
| 766 | * A Uri will always be returned, even if the Uri is badly formatted to |
| 767 | * begin with and a scheme component cannot be found. |
| 768 | * |
| 769 | * @return normalized Uri (never null) |
| 770 | * @see {@link android.content.Intent#setData} |
| 771 | * @see {@link #setNormalizedData} |
| 772 | */ |
| 773 | public static Uri normalizeUri(Uri uri) { |
| 774 | String scheme = uri.getScheme(); |
| 775 | if (scheme == null) return uri; // give up |
| 776 | String lowerScheme = scheme.toLowerCase(Locale.US); |
| 777 | if (scheme.equals(lowerScheme)) return uri; // no change |
| 778 | |
| 779 | return uri.buildUpon().scheme(lowerScheme).build(); |
| 780 | } |
| 781 | |
| 782 | public static Intent setIntentTypeAndNormalize(Intent intent, String type) { |
| 783 | return intent.setType(normalizeMimeType(type)); |
| 784 | } |
| 785 | |
| 786 | public static Intent setIntentDataAndTypeAndNormalize(Intent intent, Uri data, String type) { |
| 787 | return intent.setDataAndType(normalizeUri(data), normalizeMimeType(type)); |
| 788 | } |
| 789 | |
Mindy Pereira | b5080d5 | 2012-03-09 11:26:44 -0800 | [diff] [blame^] | 790 | public static int getDefaultFolderBackgroundColor(Context context) { |
| 791 | return Integer.parseInt(context.getResources().getString( |
| 792 | R.string.default_folder_background_color)); |
| 793 | } |
| 794 | |
| 795 | public static int getTransparentColor(int color) { |
| 796 | return 0x00ffffff & color; |
| 797 | } |
Mindy Pereira | 7b56a61 | 2011-12-14 12:32:28 -0800 | [diff] [blame] | 798 | } |