2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.android.email;
|
|
|
|
|
2010-02-14 23:22:40 +00:00
|
|
|
import com.android.email.provider.EmailContent;
|
2009-09-15 21:10:12 +00:00
|
|
|
import com.android.email.provider.EmailContent.Account;
|
|
|
|
import com.android.email.provider.EmailContent.AccountColumns;
|
|
|
|
import com.android.email.provider.EmailContent.HostAuth;
|
|
|
|
import com.android.email.provider.EmailContent.HostAuthColumns;
|
2009-08-24 22:00:45 +00:00
|
|
|
import com.android.email.provider.EmailContent.Mailbox;
|
|
|
|
import com.android.email.provider.EmailContent.MailboxColumns;
|
|
|
|
import com.android.email.provider.EmailContent.Message;
|
|
|
|
import com.android.email.provider.EmailContent.MessageColumns;
|
|
|
|
|
2010-05-24 18:35:43 +00:00
|
|
|
import android.app.Activity;
|
2009-08-24 22:00:45 +00:00
|
|
|
import android.content.ContentResolver;
|
2010-02-08 21:04:03 +00:00
|
|
|
import android.content.Context;
|
2010-06-30 00:17:12 +00:00
|
|
|
import android.content.res.Resources;
|
2010-02-08 21:04:03 +00:00
|
|
|
import android.content.res.TypedArray;
|
2009-08-24 22:00:45 +00:00
|
|
|
import android.database.Cursor;
|
2010-02-08 21:04:03 +00:00
|
|
|
import android.graphics.drawable.Drawable;
|
2010-04-06 17:25:15 +00:00
|
|
|
import android.os.AsyncTask;
|
2010-04-14 23:49:35 +00:00
|
|
|
import android.security.MessageDigest;
|
|
|
|
import android.telephony.TelephonyManager;
|
2010-02-08 21:04:03 +00:00
|
|
|
import android.text.Editable;
|
2010-04-23 01:03:00 +00:00
|
|
|
import android.text.TextUtils;
|
2010-03-29 20:23:39 +00:00
|
|
|
import android.util.Base64;
|
2010-04-14 23:49:35 +00:00
|
|
|
import android.util.Log;
|
2010-02-08 21:04:03 +00:00
|
|
|
import android.widget.TextView;
|
2010-05-24 18:35:43 +00:00
|
|
|
import android.widget.Toast;
|
2009-08-24 22:00:45 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
import java.io.ByteArrayInputStream;
|
2010-06-30 00:17:12 +00:00
|
|
|
import java.io.File;
|
2009-03-04 03:32:22 +00:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
import java.io.UnsupportedEncodingException;
|
2010-03-12 21:30:26 +00:00
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.CharBuffer;
|
|
|
|
import java.nio.charset.Charset;
|
2010-04-14 23:49:35 +00:00
|
|
|
import java.security.NoSuchAlgorithmException;
|
2009-03-04 03:32:22 +00:00
|
|
|
import java.util.Date;
|
2010-03-02 16:43:02 +00:00
|
|
|
import java.util.GregorianCalendar;
|
|
|
|
import java.util.TimeZone;
|
2010-04-23 01:03:00 +00:00
|
|
|
import java.util.regex.Pattern;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
public class Utility {
|
2010-03-12 21:30:26 +00:00
|
|
|
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
2010-05-20 00:23:23 +00:00
|
|
|
public static final Charset ASCII = Charset.forName("US-ASCII");
|
|
|
|
|
|
|
|
public static final String[] EMPTY_STRINGS = new String[0];
|
2010-03-12 21:30:26 +00:00
|
|
|
|
2010-04-23 01:03:00 +00:00
|
|
|
// "GMT" + "+" or "-" + 4 digits
|
|
|
|
private static final Pattern DATE_CLEANUP_PATTERN_WRONG_TIMEZONE =
|
|
|
|
Pattern.compile("GMT([-+]\\d{4})$");
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
public final static String readInputStream(InputStream in, String encoding) throws IOException {
|
|
|
|
InputStreamReader reader = new InputStreamReader(in, encoding);
|
|
|
|
StringBuffer sb = new StringBuffer();
|
|
|
|
int count;
|
|
|
|
char[] buf = new char[512];
|
|
|
|
while ((count = reader.read(buf)) != -1) {
|
|
|
|
sb.append(buf, 0, count);
|
|
|
|
}
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
public final static boolean arrayContains(Object[] a, Object o) {
|
|
|
|
for (int i = 0, count = a.length; i < count; i++) {
|
|
|
|
if (a[i].equals(o)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Combines the given array of Objects into a single string using the
|
|
|
|
* seperator character and each Object's toString() method. between each
|
|
|
|
* part.
|
|
|
|
*
|
|
|
|
* @param parts
|
|
|
|
* @param seperator
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
public static String combine(Object[] parts, char seperator) {
|
|
|
|
if (parts == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
StringBuffer sb = new StringBuffer();
|
|
|
|
for (int i = 0; i < parts.length; i++) {
|
|
|
|
sb.append(parts[i].toString());
|
|
|
|
if (i < parts.length - 1) {
|
|
|
|
sb.append(seperator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
public static String base64Decode(String encoded) {
|
|
|
|
if (encoded == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2010-02-08 21:04:03 +00:00
|
|
|
byte[] decoded = Base64.decode(encoded, Base64.DEFAULT);
|
2009-03-04 03:32:22 +00:00
|
|
|
return new String(decoded);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String base64Encode(String s) {
|
|
|
|
if (s == null) {
|
|
|
|
return s;
|
|
|
|
}
|
2010-02-08 21:04:03 +00:00
|
|
|
return Base64.encodeToString(s.getBytes(), Base64.NO_WRAP);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean requiredFieldValid(TextView view) {
|
|
|
|
return view.getText() != null && view.getText().length() > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean requiredFieldValid(Editable s) {
|
|
|
|
return s != null && s.length() > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
* double quote character to start and end if it's not already there.
|
2010-02-08 21:04:03 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* TODO: Rename this, because "quoteString()" can mean so many different things.
|
2010-02-08 21:04:03 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* sample -> "sample"
|
|
|
|
* "sample" -> "sample"
|
|
|
|
* ""sample"" -> "sample"
|
|
|
|
* "sample"" -> "sample"
|
|
|
|
* sa"mp"le -> "sa"mp"le"
|
|
|
|
* "sa"mp"le" -> "sa"mp"le"
|
|
|
|
* (empty string) -> ""
|
|
|
|
* " -> ""
|
|
|
|
* @param s
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
public static String quoteString(String s) {
|
|
|
|
if (s == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (!s.matches("^\".*\"$")) {
|
|
|
|
return "\"" + s + "\"";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
}
|
2010-02-08 21:04:03 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
2010-02-08 21:04:03 +00:00
|
|
|
* Apply quoting rules per IMAP RFC,
|
2009-03-04 03:32:22 +00:00
|
|
|
* quoted = DQUOTE *QUOTED-CHAR DQUOTE
|
|
|
|
* QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> / "\" quoted-specials
|
|
|
|
* quoted-specials = DQUOTE / "\"
|
2010-02-08 21:04:03 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* This is used primarily for IMAP login, but might be useful elsewhere.
|
2010-02-08 21:04:03 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* NOTE: Not very efficient - you may wish to preflight this, or perhaps it should check
|
|
|
|
* for trouble chars before calling the replace functions.
|
2010-02-08 21:04:03 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param s The string to be quoted.
|
|
|
|
* @return A copy of the string, having undergone quoting as described above
|
|
|
|
*/
|
|
|
|
public static String imapQuoted(String s) {
|
2010-02-08 21:04:03 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
// First, quote any backslashes by replacing \ with \\
|
|
|
|
// regex Pattern: \\ (Java string const = \\\\)
|
|
|
|
// Substitute: \\\\ (Java string const = \\\\\\\\)
|
|
|
|
String result = s.replaceAll("\\\\", "\\\\\\\\");
|
2010-02-08 21:04:03 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
// Then, quote any double-quotes by replacing " with \"
|
|
|
|
// regex Pattern: " (Java string const = \")
|
|
|
|
// Substitute: \\" (Java string const = \\\\\")
|
|
|
|
result = result.replaceAll("\"", "\\\\\"");
|
2010-02-08 21:04:03 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
// return string with quotes around it
|
|
|
|
return "\"" + result + "\"";
|
|
|
|
}
|
2010-02-08 21:04:03 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* A fast version of URLDecoder.decode() that works only with UTF-8 and does only two
|
|
|
|
* allocations. This version is around 3x as fast as the standard one and I'm using it
|
|
|
|
* hundreds of times in places that slow down the UI, so it helps.
|
|
|
|
*/
|
|
|
|
public static String fastUrlDecode(String s) {
|
|
|
|
try {
|
|
|
|
byte[] bytes = s.getBytes("UTF-8");
|
|
|
|
byte ch;
|
|
|
|
int length = 0;
|
|
|
|
for (int i = 0, count = bytes.length; i < count; i++) {
|
|
|
|
ch = bytes[i];
|
|
|
|
if (ch == '%') {
|
|
|
|
int h = (bytes[i + 1] - '0');
|
|
|
|
int l = (bytes[i + 2] - '0');
|
|
|
|
if (h > 9) {
|
|
|
|
h -= 7;
|
|
|
|
}
|
|
|
|
if (l > 9) {
|
|
|
|
l -= 7;
|
|
|
|
}
|
|
|
|
bytes[length] = (byte) ((h << 4) | l);
|
|
|
|
i += 2;
|
|
|
|
}
|
|
|
|
else if (ch == '+') {
|
|
|
|
bytes[length] = ' ';
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
bytes[length] = bytes[i];
|
|
|
|
}
|
|
|
|
length++;
|
|
|
|
}
|
|
|
|
return new String(bytes, 0, length, "UTF-8");
|
|
|
|
}
|
|
|
|
catch (UnsupportedEncodingException uee) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the specified date is within today. Returns false otherwise.
|
|
|
|
* @param date
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
public static boolean isDateToday(Date date) {
|
|
|
|
// TODO But Calendar is so slowwwwwww....
|
|
|
|
Date today = new Date();
|
|
|
|
if (date.getYear() == today.getYear() &&
|
|
|
|
date.getMonth() == today.getMonth() &&
|
|
|
|
date.getDate() == today.getDate()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TODO disabled this method globally. It is used in all the settings screens but I just
|
|
|
|
* noticed that an unrelated icon was dimmed. Android must share drawables internally.
|
|
|
|
*/
|
|
|
|
public static void setCompoundDrawablesAlpha(TextView view, int alpha) {
|
|
|
|
// Drawable[] drawables = view.getCompoundDrawables();
|
|
|
|
// for (Drawable drawable : drawables) {
|
|
|
|
// if (drawable != null) {
|
|
|
|
// drawable.setAlpha(alpha);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
}
|
2009-08-24 22:00:45 +00:00
|
|
|
|
|
|
|
// TODO: unit test this
|
|
|
|
public static String buildMailboxIdSelection(ContentResolver resolver, long mailboxId) {
|
|
|
|
// Setup default selection & args, then add to it as necessary
|
|
|
|
StringBuilder selection = new StringBuilder(
|
2009-09-10 18:52:36 +00:00
|
|
|
MessageColumns.FLAG_LOADED + " IN ("
|
|
|
|
+ Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE
|
|
|
|
+ ") AND ");
|
2009-08-24 22:00:45 +00:00
|
|
|
if (mailboxId == Mailbox.QUERY_ALL_INBOXES
|
|
|
|
|| mailboxId == Mailbox.QUERY_ALL_DRAFTS
|
|
|
|
|| mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
|
|
|
|
// query for all mailboxes of type INBOX, DRAFTS, or OUTBOX
|
|
|
|
int type;
|
|
|
|
if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
|
|
|
|
type = Mailbox.TYPE_INBOX;
|
|
|
|
} else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
|
|
|
|
type = Mailbox.TYPE_DRAFTS;
|
|
|
|
} else {
|
|
|
|
type = Mailbox.TYPE_OUTBOX;
|
|
|
|
}
|
|
|
|
StringBuilder inboxes = new StringBuilder();
|
|
|
|
Cursor c = resolver.query(Mailbox.CONTENT_URI,
|
|
|
|
EmailContent.ID_PROJECTION,
|
|
|
|
MailboxColumns.TYPE + "=? AND " + MailboxColumns.FLAG_VISIBLE + "=1",
|
|
|
|
new String[] { Integer.toString(type) }, null);
|
|
|
|
// build an IN (mailboxId, ...) list
|
|
|
|
// TODO do this directly in the provider
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
if (inboxes.length() != 0) {
|
|
|
|
inboxes.append(",");
|
|
|
|
}
|
|
|
|
inboxes.append(c.getLong(EmailContent.ID_PROJECTION_COLUMN));
|
|
|
|
}
|
|
|
|
c.close();
|
|
|
|
selection.append(MessageColumns.MAILBOX_KEY + " IN ");
|
|
|
|
selection.append("(").append(inboxes).append(")");
|
|
|
|
} else if (mailboxId == Mailbox.QUERY_ALL_UNREAD) {
|
|
|
|
selection.append(Message.FLAG_READ + "=0");
|
|
|
|
} else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
|
|
|
|
selection.append(Message.FLAG_FAVORITE + "=1");
|
|
|
|
} else {
|
|
|
|
selection.append(MessageColumns.MAILBOX_KEY + "=" + mailboxId);
|
|
|
|
}
|
|
|
|
return selection.toString();
|
|
|
|
}
|
2009-08-21 11:05:09 +00:00
|
|
|
|
|
|
|
public static class FolderProperties {
|
|
|
|
|
|
|
|
private static FolderProperties sInstance;
|
|
|
|
|
|
|
|
// Caches for frequently accessed resources.
|
|
|
|
private String[] mSpecialMailbox = new String[] {};
|
|
|
|
private TypedArray mSpecialMailboxDrawable;
|
|
|
|
private Drawable mDefaultMailboxDrawable;
|
2009-09-29 06:57:01 +00:00
|
|
|
private Drawable mSummaryStarredMailboxDrawable;
|
|
|
|
private Drawable mSummaryCombinedInboxDrawable;
|
2009-08-21 11:05:09 +00:00
|
|
|
|
|
|
|
private FolderProperties(Context context) {
|
2009-09-21 19:57:04 +00:00
|
|
|
mSpecialMailbox = context.getResources().getStringArray(R.array.mailbox_display_names);
|
2009-08-21 11:05:09 +00:00
|
|
|
for (int i = 0; i < mSpecialMailbox.length; ++i) {
|
|
|
|
if ("".equals(mSpecialMailbox[i])) {
|
|
|
|
// there is no localized name, so use the display name from the server
|
|
|
|
mSpecialMailbox[i] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mSpecialMailboxDrawable =
|
2009-09-21 19:57:04 +00:00
|
|
|
context.getResources().obtainTypedArray(R.array.mailbox_display_icons);
|
2009-08-21 11:05:09 +00:00
|
|
|
mDefaultMailboxDrawable =
|
|
|
|
context.getResources().getDrawable(R.drawable.ic_list_folder);
|
2009-09-29 06:57:01 +00:00
|
|
|
mSummaryStarredMailboxDrawable =
|
|
|
|
context.getResources().getDrawable(R.drawable.ic_list_starred);
|
|
|
|
mSummaryCombinedInboxDrawable =
|
|
|
|
context.getResources().getDrawable(R.drawable.ic_list_combined_inbox);
|
2009-08-21 11:05:09 +00:00
|
|
|
}
|
|
|
|
|
2010-05-07 21:04:50 +00:00
|
|
|
public static synchronized FolderProperties getInstance(Context context) {
|
2009-08-21 11:05:09 +00:00
|
|
|
if (sInstance == null) {
|
2010-05-07 21:04:50 +00:00
|
|
|
sInstance = new FolderProperties(context);
|
2009-08-21 11:05:09 +00:00
|
|
|
}
|
|
|
|
return sInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Lookup names of localized special mailboxes
|
|
|
|
* @param type
|
|
|
|
* @return Localized strings
|
|
|
|
*/
|
|
|
|
public String getDisplayName(int type) {
|
|
|
|
if (type < mSpecialMailbox.length) {
|
|
|
|
return mSpecialMailbox[type];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Lookup icons of special mailboxes
|
|
|
|
* @param type
|
|
|
|
* @return icon's drawable
|
|
|
|
*/
|
2009-09-02 19:41:16 +00:00
|
|
|
public Drawable getIconIds(int type) {
|
2009-08-21 11:05:09 +00:00
|
|
|
if (type < mSpecialMailboxDrawable.length()) {
|
|
|
|
return mSpecialMailboxDrawable.getDrawable(type);
|
|
|
|
}
|
|
|
|
return mDefaultMailboxDrawable;
|
|
|
|
}
|
2009-09-29 06:57:01 +00:00
|
|
|
|
|
|
|
public Drawable getSummaryMailboxIconIds(long mailboxKey) {
|
|
|
|
if (mailboxKey == Mailbox.QUERY_ALL_INBOXES) {
|
|
|
|
return mSummaryCombinedInboxDrawable;
|
|
|
|
} else if (mailboxKey == Mailbox.QUERY_ALL_FAVORITES) {
|
|
|
|
return mSummaryStarredMailboxDrawable;
|
|
|
|
} else if (mailboxKey == Mailbox.QUERY_ALL_DRAFTS) {
|
|
|
|
return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_DRAFTS);
|
|
|
|
} else if (mailboxKey == Mailbox.QUERY_ALL_OUTBOX) {
|
|
|
|
return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_OUTBOX);
|
|
|
|
}
|
|
|
|
return mDefaultMailboxDrawable;
|
|
|
|
}
|
2009-08-21 11:05:09 +00:00
|
|
|
}
|
2009-09-15 21:10:12 +00:00
|
|
|
|
|
|
|
private final static String HOSTAUTH_WHERE_CREDENTIALS = HostAuthColumns.ADDRESS + " like ?"
|
|
|
|
+ " and " + HostAuthColumns.LOGIN + " like ?"
|
|
|
|
+ " and " + HostAuthColumns.PROTOCOL + " not like \"smtp\"";
|
|
|
|
private final static String ACCOUNT_WHERE_HOSTAUTH = AccountColumns.HOST_AUTH_KEY_RECV + "=?";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Look for an existing account with the same username & server
|
|
|
|
*
|
|
|
|
* @param context a system context
|
|
|
|
* @param allowAccountId this account Id will not trigger (when editing an existing account)
|
2010-05-20 20:06:22 +00:00
|
|
|
* @param hostName the server's address
|
|
|
|
* @param userLogin the user's login string
|
|
|
|
* @result null = no matching account found. Account = matching account
|
2009-09-15 21:10:12 +00:00
|
|
|
*/
|
2010-05-20 20:06:22 +00:00
|
|
|
public static Account findExistingAccount(Context context, long allowAccountId,
|
|
|
|
String hostName, String userLogin) {
|
2009-09-15 21:10:12 +00:00
|
|
|
ContentResolver resolver = context.getContentResolver();
|
|
|
|
Cursor c = resolver.query(HostAuth.CONTENT_URI, HostAuth.ID_PROJECTION,
|
|
|
|
HOSTAUTH_WHERE_CREDENTIALS, new String[] { hostName, userLogin }, null);
|
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
long hostAuthId = c.getLong(HostAuth.ID_PROJECTION_COLUMN);
|
2010-05-20 20:06:22 +00:00
|
|
|
// Find account with matching hostauthrecv key, and return it
|
2009-09-15 21:10:12 +00:00
|
|
|
Cursor c2 = resolver.query(Account.CONTENT_URI, Account.ID_PROJECTION,
|
|
|
|
ACCOUNT_WHERE_HOSTAUTH, new String[] { Long.toString(hostAuthId) }, null);
|
|
|
|
try {
|
|
|
|
while (c2.moveToNext()) {
|
|
|
|
long accountId = c2.getLong(Account.ID_PROJECTION_COLUMN);
|
|
|
|
if (accountId != allowAccountId) {
|
|
|
|
Account account = Account.restoreAccountWithId(context, accountId);
|
|
|
|
if (account != null) {
|
2010-05-20 20:06:22 +00:00
|
|
|
return account;
|
2009-09-15 21:10:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c2.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
2009-10-06 21:20:09 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a random message-id header for locally-generated messages.
|
|
|
|
*/
|
|
|
|
public static String generateMessageId() {
|
|
|
|
StringBuffer sb = new StringBuffer();
|
|
|
|
sb.append("<");
|
|
|
|
for (int i = 0; i < 24; i++) {
|
|
|
|
sb.append(Integer.toString((int)(Math.random() * 35), 36));
|
|
|
|
}
|
|
|
|
sb.append(".");
|
|
|
|
sb.append(Long.toString(System.currentTimeMillis()));
|
|
|
|
sb.append("@email.android.com>");
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
2010-03-02 16:43:02 +00:00
|
|
|
/**
|
|
|
|
* Generate a time in milliseconds from a date string that represents a date/time in GMT
|
|
|
|
* @param DateTime date string in format 20090211T180303Z (rfc2445, iCalendar).
|
|
|
|
* @return the time in milliseconds (since Jan 1, 1970)
|
|
|
|
*/
|
|
|
|
public static long parseDateTimeToMillis(String date) {
|
|
|
|
GregorianCalendar cal = parseDateTimeToCalendar(date);
|
|
|
|
return cal.getTimeInMillis();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a GregorianCalendar from a date string that represents a date/time in GMT
|
|
|
|
* @param DateTime date string in format 20090211T180303Z (rfc2445, iCalendar).
|
|
|
|
* @return the GregorianCalendar
|
|
|
|
*/
|
|
|
|
public static GregorianCalendar parseDateTimeToCalendar(String date) {
|
|
|
|
GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
|
|
|
|
Integer.parseInt(date.substring(4, 6)) - 1, Integer.parseInt(date.substring(6, 8)),
|
|
|
|
Integer.parseInt(date.substring(9, 11)), Integer.parseInt(date.substring(11, 13)),
|
|
|
|
Integer.parseInt(date.substring(13, 15)));
|
|
|
|
cal.setTimeZone(TimeZone.getTimeZone("GMT"));
|
|
|
|
return cal;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a time in milliseconds from an email date string that represents a date/time in GMT
|
|
|
|
* @param Email style DateTime string in format 2010-02-23T16:00:00.000Z (ISO 8601, rfc3339)
|
|
|
|
* @return the time in milliseconds (since Jan 1, 1970)
|
|
|
|
*/
|
|
|
|
public static long parseEmailDateTimeToMillis(String date) {
|
|
|
|
GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
|
|
|
|
Integer.parseInt(date.substring(5, 7)) - 1, Integer.parseInt(date.substring(8, 10)),
|
|
|
|
Integer.parseInt(date.substring(11, 13)), Integer.parseInt(date.substring(14, 16)),
|
|
|
|
Integer.parseInt(date.substring(17, 19)));
|
|
|
|
cal.setTimeZone(TimeZone.getTimeZone("GMT"));
|
|
|
|
return cal.getTimeInMillis();
|
|
|
|
}
|
2010-03-12 21:30:26 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
private static byte[] encode(Charset charset, String s) {
|
2010-03-12 21:30:26 +00:00
|
|
|
if (s == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
final ByteBuffer buffer = charset.encode(CharBuffer.wrap(s));
|
2010-03-12 21:30:26 +00:00
|
|
|
final byte[] bytes = new byte[buffer.limit()];
|
|
|
|
buffer.get(bytes);
|
|
|
|
return bytes;
|
|
|
|
}
|
2010-03-16 18:08:46 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
private static String decode(Charset charset, byte[] b) {
|
2010-03-24 18:05:14 +00:00
|
|
|
if (b == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
final CharBuffer cb = charset.decode(ByteBuffer.wrap(b));
|
2010-03-24 18:05:14 +00:00
|
|
|
return new String(cb.array(), 0, cb.length());
|
|
|
|
}
|
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
/** Converts a String to UTF-8 */
|
|
|
|
public static byte[] toUtf8(String s) {
|
|
|
|
return encode(UTF_8, s);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Builds a String from UTF-8 bytes */
|
|
|
|
public static String fromUtf8(byte[] b) {
|
|
|
|
return decode(UTF_8, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Converts a String to ASCII bytes */
|
|
|
|
public static byte[] toAscii(String s) {
|
|
|
|
return encode(ASCII, s);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Builds a String from ASCII bytes */
|
|
|
|
public static String fromAscii(byte[] b) {
|
|
|
|
return decode(ASCII, b);
|
|
|
|
}
|
|
|
|
|
2010-03-16 18:08:46 +00:00
|
|
|
/**
|
|
|
|
* @return true if the input is the first (or only) byte in a UTF-8 character
|
|
|
|
*/
|
|
|
|
public static boolean isFirstUtf8Byte(byte b) {
|
|
|
|
// If the top 2 bits is '10', it's not a first byte.
|
|
|
|
return (b & 0xc0) != 0x80;
|
|
|
|
}
|
2010-03-17 18:26:57 +00:00
|
|
|
|
|
|
|
public static String byteToHex(int b) {
|
|
|
|
return byteToHex(new StringBuilder(), b).toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static StringBuilder byteToHex(StringBuilder sb, int b) {
|
|
|
|
b &= 0xFF;
|
|
|
|
sb.append("0123456789ABCDEF".charAt(b >> 4));
|
|
|
|
sb.append("0123456789ABCDEF".charAt(b & 0xF));
|
|
|
|
return sb;
|
|
|
|
}
|
2010-03-25 19:12:44 +00:00
|
|
|
|
|
|
|
public static String replaceBareLfWithCrlf(String str) {
|
|
|
|
return str.replace("\r", "").replace("\n", "\r\n");
|
|
|
|
}
|
2010-04-06 17:25:15 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Cancel an {@link AsyncTask}. If it's already running, it'll be interrupted.
|
|
|
|
*/
|
|
|
|
public static void cancelTaskInterrupt(AsyncTask<?, ?, ?> task) {
|
|
|
|
cancelTask(task, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cancel an {@link AsyncTask}.
|
|
|
|
*
|
|
|
|
* @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
|
|
|
|
* task should be interrupted; otherwise, in-progress tasks are allowed
|
|
|
|
* to complete.
|
|
|
|
*/
|
|
|
|
public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) {
|
|
|
|
if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) {
|
|
|
|
task.cancel(mayInterruptIfRunning);
|
|
|
|
}
|
|
|
|
}
|
2010-04-14 23:49:35 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Device's unique ID if available. null if the device has no unique ID.
|
|
|
|
*/
|
|
|
|
public static String getConsistentDeviceId(Context context) {
|
|
|
|
final String deviceId;
|
|
|
|
try {
|
|
|
|
TelephonyManager tm =
|
|
|
|
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
|
|
|
if (tm == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
deviceId = tm.getDeviceId();
|
|
|
|
if (deviceId == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
Log.d(Email.LOG_TAG, "Error in TelephonyManager.getDeviceId(): " + e.getMessage());
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
final MessageDigest sha;
|
|
|
|
try {
|
|
|
|
sha = MessageDigest.getInstance("SHA-1");
|
|
|
|
} catch (NoSuchAlgorithmException impossible) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
sha.update(Utility.toUtf8(deviceId));
|
|
|
|
final int hash = getSmallHashFromSha1(sha.digest());
|
|
|
|
return Integer.toString(hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return a non-negative integer generated from 20 byte SHA-1 hash.
|
|
|
|
*/
|
|
|
|
/* package for testing */ static int getSmallHashFromSha1(byte[] sha1) {
|
|
|
|
final int offset = sha1[19] & 0xf; // SHA1 is 20 bytes.
|
|
|
|
return ((sha1[offset] & 0x7f) << 24)
|
|
|
|
| ((sha1[offset + 1] & 0xff) << 16)
|
|
|
|
| ((sha1[offset + 2] & 0xff) << 8)
|
|
|
|
| ((sha1[offset + 3] & 0xff));
|
|
|
|
}
|
2010-04-23 01:03:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Try to make a date MIME(RFC 2822/5322)-compliant.
|
|
|
|
*
|
|
|
|
* It fixes:
|
|
|
|
* - "Thu, 10 Dec 09 15:08:08 GMT-0700" to "Thu, 10 Dec 09 15:08:08 -0700"
|
|
|
|
* (4 digit zone value can't be preceded by "GMT")
|
|
|
|
* We got a report saying eBay sends a date in this format
|
|
|
|
*/
|
|
|
|
public static String cleanUpMimeDate(String date) {
|
|
|
|
if (TextUtils.isEmpty(date)) {
|
|
|
|
return date;
|
|
|
|
}
|
|
|
|
date = DATE_CLEANUP_PATTERN_WRONG_TIMEZONE.matcher(date).replaceFirst("$1");
|
|
|
|
return date;
|
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
|
|
|
|
public static ByteArrayInputStream streamFromAsciiString(String ascii) {
|
|
|
|
return new ByteArrayInputStream(toAscii(ascii));
|
|
|
|
}
|
2010-05-24 18:35:43 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A thread safe way to show a Toast. This method uses {@link Activity#runOnUiThread}, so it
|
|
|
|
* can be called on any thread.
|
|
|
|
*
|
|
|
|
* @param activity Parent activity.
|
|
|
|
* @param resId Resource ID of the message string.
|
|
|
|
*/
|
2010-06-17 21:57:49 +00:00
|
|
|
public static void showToast(Activity activity, int resId) {
|
|
|
|
showToast(activity, activity.getResources().getString(resId));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A thread safe way to show a Toast. This method uses {@link Activity#runOnUiThread}, so it
|
|
|
|
* can be called on any thread.
|
|
|
|
*
|
|
|
|
* @param activity Parent activity.
|
|
|
|
* @param message Message to show.
|
|
|
|
*/
|
|
|
|
public static void showToast(final Activity activity, final String message) {
|
2010-05-24 18:35:43 +00:00
|
|
|
activity.runOnUiThread(new Runnable() {
|
|
|
|
public void run() {
|
2010-06-17 21:57:49 +00:00
|
|
|
Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
|
2010-05-24 18:35:43 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2010-06-07 21:03:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Run {@code r} on a worker thread.
|
|
|
|
*/
|
|
|
|
public static void runAsync(final Runnable r) {
|
|
|
|
new AsyncTask<Void, Void, Void>() {
|
|
|
|
@Override protected Void doInBackground(Void... params) {
|
|
|
|
r.run();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}.execute();
|
|
|
|
}
|
2010-06-30 00:17:12 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Formats the given size as a String in bytes, kB, MB or GB. Ex: 12,315,000 = 11 MB
|
|
|
|
*/
|
|
|
|
public static String formatSize(Context context, long size) {
|
|
|
|
final Resources res = context.getResources();
|
|
|
|
final long KB = 1024;
|
|
|
|
final long MB = (KB * 1024);
|
|
|
|
final long GB = (MB * 1024);
|
|
|
|
|
|
|
|
int resId;
|
|
|
|
int value;
|
|
|
|
|
|
|
|
if (size < KB) {
|
|
|
|
resId = R.plurals.message_view_attachment_bytes;
|
|
|
|
value = (int) size;
|
|
|
|
} else if (size < MB) {
|
|
|
|
resId = R.plurals.message_view_attachment_kilobytes;
|
|
|
|
value = (int) (size / KB);
|
|
|
|
} else if (size < GB) {
|
|
|
|
resId = R.plurals.message_view_attachment_megabytes;
|
|
|
|
value = (int) (size / MB);
|
|
|
|
} else {
|
|
|
|
resId = R.plurals.message_view_attachment_gigabytes;
|
|
|
|
value = (int) (size / GB);
|
|
|
|
}
|
|
|
|
return res.getQuantityString(resId, value, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Interface used in {@link #createUniqueFile} instead of {@link File#createNewFile()} to make
|
|
|
|
* it testable.
|
|
|
|
*/
|
|
|
|
/* package */ interface NewFileCreator {
|
|
|
|
public static final NewFileCreator DEFAULT = new NewFileCreator() {
|
|
|
|
@Override public boolean createNewFile(File f) throws IOException {
|
|
|
|
return f.createNewFile();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
public boolean createNewFile(File f) throws IOException ;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new empty file with a unique name in the given directory by appending a hyphen and
|
|
|
|
* a number to the given filename.
|
|
|
|
*
|
|
|
|
* @return a new File object, or null if one could not be created
|
|
|
|
*/
|
|
|
|
public static File createUniqueFile(File directory, String filename) throws IOException {
|
|
|
|
return createUniqueFileInternal(NewFileCreator.DEFAULT, directory, filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* package */ static File createUniqueFileInternal(NewFileCreator nfc,
|
|
|
|
File directory, String filename) throws IOException {
|
|
|
|
File file = new File(directory, filename);
|
|
|
|
if (nfc.createNewFile(file)) {
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
// Get the extension of the file, if any.
|
|
|
|
int index = filename.lastIndexOf('.');
|
|
|
|
String format;
|
|
|
|
if (index != -1) {
|
|
|
|
String name = filename.substring(0, index);
|
|
|
|
String extension = filename.substring(index);
|
|
|
|
format = name + "-%d" + extension;
|
|
|
|
} else {
|
|
|
|
format = filename + "-%d";
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 2; i < Integer.MAX_VALUE; i++) {
|
|
|
|
file = new File(directory, String.format(format, i));
|
|
|
|
if (nfc.createNewFile(file)) {
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|