1 /* 2 * Copyright (C) 2008 The Android Open Source Project 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 */ 16 17 package com.android.email; 18 19 import com.android.email.provider.EmailContent; 20 import com.android.email.provider.EmailContent.Account; 21 import com.android.email.provider.EmailContent.AccountColumns; 22 import com.android.email.provider.EmailContent.HostAuth; 23 import com.android.email.provider.EmailContent.HostAuthColumns; 24 import com.android.email.provider.EmailContent.Mailbox; 25 import com.android.email.provider.EmailContent.MailboxColumns; 26 import com.android.email.provider.EmailContent.Message; 27 import com.android.email.provider.EmailContent.MessageColumns; 28 29 import android.content.ContentResolver; 30 import android.database.Cursor; 31 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.InputStreamReader; 35 import java.io.UnsupportedEncodingException; 36 import java.util.Date; 37 38 import com.android.email.codec.binary.Base64; 39 40 import android.content.Context; 41 import android.content.res.TypedArray; 42 import android.graphics.drawable.Drawable; 43 import android.text.Editable; 44 import android.widget.TextView; 45 46 public class Utility { readInputStream(InputStream in, String encoding)47 public final static String readInputStream(InputStream in, String encoding) throws IOException { 48 InputStreamReader reader = new InputStreamReader(in, encoding); 49 StringBuffer sb = new StringBuffer(); 50 int count; 51 char[] buf = new char[512]; 52 while ((count = reader.read(buf)) != -1) { 53 sb.append(buf, 0, count); 54 } 55 return sb.toString(); 56 } 57 arrayContains(Object[] a, Object o)58 public final static boolean arrayContains(Object[] a, Object o) { 59 for (int i = 0, count = a.length; i < count; i++) { 60 if (a[i].equals(o)) { 61 return true; 62 } 63 } 64 return false; 65 } 66 67 /** 68 * Combines the given array of Objects into a single string using the 69 * seperator character and each Object's toString() method. between each 70 * part. 71 * 72 * @param parts 73 * @param seperator 74 * @return 75 */ combine(Object[] parts, char seperator)76 public static String combine(Object[] parts, char seperator) { 77 if (parts == null) { 78 return null; 79 } 80 StringBuffer sb = new StringBuffer(); 81 for (int i = 0; i < parts.length; i++) { 82 sb.append(parts[i].toString()); 83 if (i < parts.length - 1) { 84 sb.append(seperator); 85 } 86 } 87 return sb.toString(); 88 } 89 base64Decode(String encoded)90 public static String base64Decode(String encoded) { 91 if (encoded == null) { 92 return null; 93 } 94 byte[] decoded = new Base64().decode(encoded.getBytes()); 95 return new String(decoded); 96 } 97 base64Encode(String s)98 public static String base64Encode(String s) { 99 if (s == null) { 100 return s; 101 } 102 byte[] encoded = new Base64().encode(s.getBytes()); 103 return new String(encoded); 104 } 105 requiredFieldValid(TextView view)106 public static boolean requiredFieldValid(TextView view) { 107 return view.getText() != null && view.getText().length() > 0; 108 } 109 requiredFieldValid(Editable s)110 public static boolean requiredFieldValid(Editable s) { 111 return s != null && s.length() > 0; 112 } 113 114 /** 115 * Ensures that the given string starts and ends with the double quote character. The string is not modified in any way except to add the 116 * double quote character to start and end if it's not already there. 117 * 118 * TODO: Rename this, because "quoteString()" can mean so many different things. 119 * 120 * sample -> "sample" 121 * "sample" -> "sample" 122 * ""sample"" -> "sample" 123 * "sample"" -> "sample" 124 * sa"mp"le -> "sa"mp"le" 125 * "sa"mp"le" -> "sa"mp"le" 126 * (empty string) -> "" 127 * " -> "" 128 * @param s 129 * @return 130 */ quoteString(String s)131 public static String quoteString(String s) { 132 if (s == null) { 133 return null; 134 } 135 if (!s.matches("^\".*\"$")) { 136 return "\"" + s + "\""; 137 } 138 else { 139 return s; 140 } 141 } 142 143 /** 144 * Apply quoting rules per IMAP RFC, 145 * quoted = DQUOTE *QUOTED-CHAR DQUOTE 146 * QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> / "\" quoted-specials 147 * quoted-specials = DQUOTE / "\" 148 * 149 * This is used primarily for IMAP login, but might be useful elsewhere. 150 * 151 * NOTE: Not very efficient - you may wish to preflight this, or perhaps it should check 152 * for trouble chars before calling the replace functions. 153 * 154 * @param s The string to be quoted. 155 * @return A copy of the string, having undergone quoting as described above 156 */ imapQuoted(String s)157 public static String imapQuoted(String s) { 158 159 // First, quote any backslashes by replacing \ with \\ 160 // regex Pattern: \\ (Java string const = \\\\) 161 // Substitute: \\\\ (Java string const = \\\\\\\\) 162 String result = s.replaceAll("\\\\", "\\\\\\\\"); 163 164 // Then, quote any double-quotes by replacing " with \" 165 // regex Pattern: " (Java string const = \") 166 // Substitute: \\" (Java string const = \\\\\") 167 result = result.replaceAll("\"", "\\\\\""); 168 169 // return string with quotes around it 170 return "\"" + result + "\""; 171 } 172 173 /** 174 * A fast version of URLDecoder.decode() that works only with UTF-8 and does only two 175 * allocations. This version is around 3x as fast as the standard one and I'm using it 176 * hundreds of times in places that slow down the UI, so it helps. 177 */ fastUrlDecode(String s)178 public static String fastUrlDecode(String s) { 179 try { 180 byte[] bytes = s.getBytes("UTF-8"); 181 byte ch; 182 int length = 0; 183 for (int i = 0, count = bytes.length; i < count; i++) { 184 ch = bytes[i]; 185 if (ch == '%') { 186 int h = (bytes[i + 1] - '0'); 187 int l = (bytes[i + 2] - '0'); 188 if (h > 9) { 189 h -= 7; 190 } 191 if (l > 9) { 192 l -= 7; 193 } 194 bytes[length] = (byte) ((h << 4) | l); 195 i += 2; 196 } 197 else if (ch == '+') { 198 bytes[length] = ' '; 199 } 200 else { 201 bytes[length] = bytes[i]; 202 } 203 length++; 204 } 205 return new String(bytes, 0, length, "UTF-8"); 206 } 207 catch (UnsupportedEncodingException uee) { 208 return null; 209 } 210 } 211 212 /** 213 * Returns true if the specified date is within today. Returns false otherwise. 214 * @param date 215 * @return 216 */ isDateToday(Date date)217 public static boolean isDateToday(Date date) { 218 // TODO But Calendar is so slowwwwwww.... 219 Date today = new Date(); 220 if (date.getYear() == today.getYear() && 221 date.getMonth() == today.getMonth() && 222 date.getDate() == today.getDate()) { 223 return true; 224 } 225 return false; 226 } 227 228 /* 229 * TODO disabled this method globally. It is used in all the settings screens but I just 230 * noticed that an unrelated icon was dimmed. Android must share drawables internally. 231 */ setCompoundDrawablesAlpha(TextView view, int alpha)232 public static void setCompoundDrawablesAlpha(TextView view, int alpha) { 233 // Drawable[] drawables = view.getCompoundDrawables(); 234 // for (Drawable drawable : drawables) { 235 // if (drawable != null) { 236 // drawable.setAlpha(alpha); 237 // } 238 // } 239 } 240 241 // TODO: unit test this buildMailboxIdSelection(ContentResolver resolver, long mailboxId)242 public static String buildMailboxIdSelection(ContentResolver resolver, long mailboxId) { 243 // Setup default selection & args, then add to it as necessary 244 StringBuilder selection = new StringBuilder( 245 MessageColumns.FLAG_LOADED + " IN (" 246 + Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE 247 + ") AND "); 248 if (mailboxId == Mailbox.QUERY_ALL_INBOXES 249 || mailboxId == Mailbox.QUERY_ALL_DRAFTS 250 || mailboxId == Mailbox.QUERY_ALL_OUTBOX) { 251 // query for all mailboxes of type INBOX, DRAFTS, or OUTBOX 252 int type; 253 if (mailboxId == Mailbox.QUERY_ALL_INBOXES) { 254 type = Mailbox.TYPE_INBOX; 255 } else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) { 256 type = Mailbox.TYPE_DRAFTS; 257 } else { 258 type = Mailbox.TYPE_OUTBOX; 259 } 260 StringBuilder inboxes = new StringBuilder(); 261 Cursor c = resolver.query(Mailbox.CONTENT_URI, 262 EmailContent.ID_PROJECTION, 263 MailboxColumns.TYPE + "=? AND " + MailboxColumns.FLAG_VISIBLE + "=1", 264 new String[] { Integer.toString(type) }, null); 265 // build an IN (mailboxId, ...) list 266 // TODO do this directly in the provider 267 while (c.moveToNext()) { 268 if (inboxes.length() != 0) { 269 inboxes.append(","); 270 } 271 inboxes.append(c.getLong(EmailContent.ID_PROJECTION_COLUMN)); 272 } 273 c.close(); 274 selection.append(MessageColumns.MAILBOX_KEY + " IN "); 275 selection.append("(").append(inboxes).append(")"); 276 } else if (mailboxId == Mailbox.QUERY_ALL_UNREAD) { 277 selection.append(Message.FLAG_READ + "=0"); 278 } else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) { 279 selection.append(Message.FLAG_FAVORITE + "=1"); 280 } else { 281 selection.append(MessageColumns.MAILBOX_KEY + "=" + mailboxId); 282 } 283 return selection.toString(); 284 } 285 286 public static class FolderProperties { 287 288 private static FolderProperties sInstance; 289 290 // Caches for frequently accessed resources. 291 private String[] mSpecialMailbox = new String[] {}; 292 private TypedArray mSpecialMailboxDrawable; 293 private Drawable mDefaultMailboxDrawable; 294 private Drawable mSummaryStarredMailboxDrawable; 295 private Drawable mSummaryCombinedInboxDrawable; 296 FolderProperties(Context context)297 private FolderProperties(Context context) { 298 mSpecialMailbox = context.getResources().getStringArray(R.array.mailbox_display_names); 299 for (int i = 0; i < mSpecialMailbox.length; ++i) { 300 if ("".equals(mSpecialMailbox[i])) { 301 // there is no localized name, so use the display name from the server 302 mSpecialMailbox[i] = null; 303 } 304 } 305 mSpecialMailboxDrawable = 306 context.getResources().obtainTypedArray(R.array.mailbox_display_icons); 307 mDefaultMailboxDrawable = 308 context.getResources().getDrawable(R.drawable.ic_list_folder); 309 mSummaryStarredMailboxDrawable = 310 context.getResources().getDrawable(R.drawable.ic_list_starred); 311 mSummaryCombinedInboxDrawable = 312 context.getResources().getDrawable(R.drawable.ic_list_combined_inbox); 313 } 314 getInstance(Context context)315 public static FolderProperties getInstance(Context context) { 316 if (sInstance == null) { 317 synchronized (FolderProperties.class) { 318 if (sInstance == null) { 319 sInstance = new FolderProperties(context); 320 } 321 } 322 } 323 return sInstance; 324 } 325 326 /** 327 * Lookup names of localized special mailboxes 328 * @param type 329 * @return Localized strings 330 */ getDisplayName(int type)331 public String getDisplayName(int type) { 332 if (type < mSpecialMailbox.length) { 333 return mSpecialMailbox[type]; 334 } 335 return null; 336 } 337 338 /** 339 * Lookup icons of special mailboxes 340 * @param type 341 * @return icon's drawable 342 */ getIconIds(int type)343 public Drawable getIconIds(int type) { 344 if (type < mSpecialMailboxDrawable.length()) { 345 return mSpecialMailboxDrawable.getDrawable(type); 346 } 347 return mDefaultMailboxDrawable; 348 } 349 getSummaryMailboxIconIds(long mailboxKey)350 public Drawable getSummaryMailboxIconIds(long mailboxKey) { 351 if (mailboxKey == Mailbox.QUERY_ALL_INBOXES) { 352 return mSummaryCombinedInboxDrawable; 353 } else if (mailboxKey == Mailbox.QUERY_ALL_FAVORITES) { 354 return mSummaryStarredMailboxDrawable; 355 } else if (mailboxKey == Mailbox.QUERY_ALL_DRAFTS) { 356 return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_DRAFTS); 357 } else if (mailboxKey == Mailbox.QUERY_ALL_OUTBOX) { 358 return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_OUTBOX); 359 } 360 return mDefaultMailboxDrawable; 361 } 362 } 363 364 private final static String HOSTAUTH_WHERE_CREDENTIALS = HostAuthColumns.ADDRESS + " like ?" 365 + " and " + HostAuthColumns.LOGIN + " like ?" 366 + " and " + HostAuthColumns.PROTOCOL + " not like \"smtp\""; 367 private final static String ACCOUNT_WHERE_HOSTAUTH = AccountColumns.HOST_AUTH_KEY_RECV + "=?"; 368 369 /** 370 * Look for an existing account with the same username & server 371 * 372 * @param context a system context 373 * @param allowAccountId this account Id will not trigger (when editing an existing account) 374 * @param hostName the server 375 * @param userLogin the user login string 376 * @result null = no dupes found. non-null = dupe account's display name 377 */ findDuplicateAccount(Context context, long allowAccountId, String hostName, String userLogin)378 public static String findDuplicateAccount(Context context, long allowAccountId, String hostName, 379 String userLogin) { 380 ContentResolver resolver = context.getContentResolver(); 381 Cursor c = resolver.query(HostAuth.CONTENT_URI, HostAuth.ID_PROJECTION, 382 HOSTAUTH_WHERE_CREDENTIALS, new String[] { hostName, userLogin }, null); 383 try { 384 while (c.moveToNext()) { 385 long hostAuthId = c.getLong(HostAuth.ID_PROJECTION_COLUMN); 386 // Find account with matching hostauthrecv key, and return its display name 387 Cursor c2 = resolver.query(Account.CONTENT_URI, Account.ID_PROJECTION, 388 ACCOUNT_WHERE_HOSTAUTH, new String[] { Long.toString(hostAuthId) }, null); 389 try { 390 while (c2.moveToNext()) { 391 long accountId = c2.getLong(Account.ID_PROJECTION_COLUMN); 392 if (accountId != allowAccountId) { 393 Account account = Account.restoreAccountWithId(context, accountId); 394 if (account != null) { 395 return account.mDisplayName; 396 } 397 } 398 } 399 } finally { 400 c2.close(); 401 } 402 } 403 } finally { 404 c.close(); 405 } 406 407 return null; 408 } 409 410 /** 411 * Generate a random message-id header for locally-generated messages. 412 */ generateMessageId()413 public static String generateMessageId() { 414 StringBuffer sb = new StringBuffer(); 415 sb.append("<"); 416 for (int i = 0; i < 24; i++) { 417 sb.append(Integer.toString((int)(Math.random() * 35), 36)); 418 } 419 sb.append("."); 420 sb.append(Long.toString(System.currentTimeMillis())); 421 sb.append("@email.android.com>"); 422 return sb.toString(); 423 } 424 425 } 426