Utility.java revision 4dcb1c5fdaacc40309b77af2a32532bc60218523
196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project/*
296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project
396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * you may not use this file except in compliance with the License.
696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * You may obtain a copy of the License at
796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
1096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
1196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
1296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * See the License for the specific language governing permissions and
1496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * limitations under the License.
1596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */
1696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
1796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpackage com.android.email;
1896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
1933c972e0c62e474d2b2f5a293b92893cac0ea47aDoug Zongkerimport com.android.email.provider.EmailContent;
20da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadlerimport com.android.email.provider.EmailContent.Account;
21da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadlerimport com.android.email.provider.EmailContent.AccountColumns;
2209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport com.android.email.provider.EmailContent.Attachment;
2309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport com.android.email.provider.EmailContent.AttachmentColumns;
24da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadlerimport com.android.email.provider.EmailContent.HostAuth;
25da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadlerimport com.android.email.provider.EmailContent.HostAuthColumns;
26e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadlerimport com.android.email.provider.EmailContent.Mailbox;
27e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadlerimport com.android.email.provider.EmailContent.MailboxColumns;
28e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadlerimport com.android.email.provider.EmailContent.Message;
29e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadlerimport com.android.email.provider.EmailContent.MessageColumns;
30e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler
317e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onukiimport android.app.Activity;
32e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadlerimport android.content.ContentResolver;
3309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.ContentUris;
3409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.ContentValues;
35f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongkerimport android.content.Context;
36533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onukiimport android.content.pm.ActivityInfo;
3744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onukiimport android.content.res.Resources;
38f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongkerimport android.content.res.TypedArray;
39e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadlerimport android.database.Cursor;
40f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongkerimport android.graphics.drawable.Drawable;
41bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onukiimport android.net.Uri;
4259cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onukiimport android.os.AsyncTask;
4309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.os.Environment;
447fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onukiimport android.os.Parcel;
45833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onukiimport android.os.Parcelable;
46d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onukiimport android.security.MessageDigest;
47d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onukiimport android.telephony.TelephonyManager;
48128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onukiimport android.text.TextUtils;
496cec1104fe8863fce2ee86ff5145076e6c436a00Doug Zongkerimport android.util.Base64;
50d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onukiimport android.util.Log;
51833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onukiimport android.widget.AbsListView;
52f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongkerimport android.widget.TextView;
537e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onukiimport android.widget.Toast;
54e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler
557e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.io.ByteArrayInputStream;
5644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onukiimport java.io.File;
57d755cdce13217d25ac33169b5e410709636255c4Makoto Onukiimport java.io.FileNotFoundException;
5896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.IOException;
5996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStream;
6096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStreamReader;
6196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.UnsupportedEncodingException;
6220225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.ByteBuffer;
6320225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.CharBuffer;
6420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.charset.Charset;
65d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onukiimport java.security.NoSuchAlgorithmException;
66f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onukiimport java.util.ArrayList;
6796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.util.Date;
68989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Predaimport java.util.GregorianCalendar;
69989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Predaimport java.util.TimeZone;
70128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onukiimport java.util.regex.Pattern;
7196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
7296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpublic class Utility {
7320225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki    public static final Charset UTF_8 = Charset.forName("UTF-8");
747e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static final Charset ASCII = Charset.forName("US-ASCII");
757e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
767e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static final String[] EMPTY_STRINGS = new String[0];
77f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    public static final Long[] EMPTY_LONGS = new Long[0];
7820225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki
79128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    // "GMT" + "+" or "-" + 4 digits
80128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    private static final Pattern DATE_CLEANUP_PATTERN_WRONG_TIMEZONE =
81128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki            Pattern.compile("GMT([-+]\\d{4})$");
82128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki
8396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public final static String readInputStream(InputStream in, String encoding) throws IOException {
8496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        InputStreamReader reader = new InputStreamReader(in, encoding);
8596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
8696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        int count;
8796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        char[] buf = new char[512];
8896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        while ((count = reader.read(buf)) != -1) {
8996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            sb.append(buf, 0, count);
9096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
9196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return sb.toString();
9296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
9396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
9496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public final static boolean arrayContains(Object[] a, Object o) {
9596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        for (int i = 0, count = a.length; i < count; i++) {
9696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (a[i].equals(o)) {
9796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                return true;
9896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
9996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
10096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return false;
10196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
10296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
10396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
10496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Combines the given array of Objects into a single string using the
10596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * seperator character and each Object's toString() method. between each
10696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * part.
10796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     *
10896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param parts
10996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param seperator
11096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @return
11196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
11296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String combine(Object[] parts, char seperator) {
11396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (parts == null) {
11496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
11596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
11696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
11796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        for (int i = 0; i < parts.length; i++) {
11896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            sb.append(parts[i].toString());
11996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (i < parts.length - 1) {
12096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                sb.append(seperator);
12196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
12296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
12396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return sb.toString();
12496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
12596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String base64Decode(String encoded) {
12696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (encoded == null) {
12796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
12896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
129f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker        byte[] decoded = Base64.decode(encoded, Base64.DEFAULT);
13096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return new String(decoded);
13196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
13296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
13396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String base64Encode(String s) {
13496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (s == null) {
13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return s;
13696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
137f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker        return Base64.encodeToString(s.getBytes(), Base64.NO_WRAP);
13896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
13996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
140e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank    public static boolean isTextViewNotEmpty(TextView view) {
141e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        return !TextUtils.isEmpty(view.getText());
14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
14396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
144e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank    public static boolean isPortFieldValid(TextView view) {
145e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        CharSequence chars = view.getText();
146e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        if (TextUtils.isEmpty(chars)) return false;
147e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        Integer port;
148e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        // In theory, we can't get an illegal value here, since the field is monitored for valid
149e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        // numeric input. But this might be used elsewhere without such a check.
150e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        try {
151e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank            port = Integer.parseInt(chars.toString());
152e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        } catch (NumberFormatException e) {
153e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank            return false;
154e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        }
155e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        return port > 0 && port < 65536;
156e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     }
15796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
15896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
159e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     * Ensures that the given string starts and ends with the double quote character. The string is
160e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     * not modified in any way except to add the double quote character to start and end if it's not
161e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     * already there.
162f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
16396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * TODO: Rename this, because "quoteString()" can mean so many different things.
164f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
16596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * sample -> "sample"
16696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * "sample" -> "sample"
16796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * ""sample"" -> "sample"
16896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * "sample"" -> "sample"
16996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * sa"mp"le -> "sa"mp"le"
17096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * "sa"mp"le" -> "sa"mp"le"
17196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * (empty string) -> ""
17296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * " -> ""
17396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param s
17496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @return
17596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
17696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String quoteString(String s) {
17796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (s == null) {
17896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
17996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
18096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (!s.matches("^\".*\"$")) {
18196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return "\"" + s + "\"";
18296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
18396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        else {
18496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return s;
18596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
18696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
187f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
18896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
189f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     * Apply quoting rules per IMAP RFC,
19096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * quoted          = DQUOTE *QUOTED-CHAR DQUOTE
19196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * QUOTED-CHAR     = <any TEXT-CHAR except quoted-specials> / "\" quoted-specials
19296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * quoted-specials = DQUOTE / "\"
193f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
19496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * This is used primarily for IMAP login, but might be useful elsewhere.
195f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
19696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * NOTE:  Not very efficient - you may wish to preflight this, or perhaps it should check
19796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * for trouble chars before calling the replace functions.
198f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
19996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param s The string to be quoted.
20096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @return A copy of the string, having undergone quoting as described above
20196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
20296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String imapQuoted(String s) {
203f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
20496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // First, quote any backslashes by replacing \ with \\
20596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // regex Pattern:  \\    (Java string const = \\\\)
20696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // Substitute:     \\\\  (Java string const = \\\\\\\\)
20796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        String result = s.replaceAll("\\\\", "\\\\\\\\");
208f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
20996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // Then, quote any double-quotes by replacing " with \"
21096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // regex Pattern:  "    (Java string const = \")
21196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // Substitute:     \\"  (Java string const = \\\\\")
21296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        result = result.replaceAll("\"", "\\\\\"");
213f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
21496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // return string with quotes around it
21596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return "\"" + result + "\"";
21696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
217f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
21896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
21996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * A fast version of  URLDecoder.decode() that works only with UTF-8 and does only two
22096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * allocations. This version is around 3x as fast as the standard one and I'm using it
22196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * hundreds of times in places that slow down the UI, so it helps.
22296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
22396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String fastUrlDecode(String s) {
22496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
22596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            byte[] bytes = s.getBytes("UTF-8");
22696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            byte ch;
22796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            int length = 0;
22896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            for (int i = 0, count = bytes.length; i < count; i++) {
22996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                ch = bytes[i];
23096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                if (ch == '%') {
23196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    int h = (bytes[i + 1] - '0');
23296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    int l = (bytes[i + 2] - '0');
23396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    if (h > 9) {
23496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                        h -= 7;
23596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    }
23696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    if (l > 9) {
23796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                        l -= 7;
23896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    }
23996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = (byte) ((h << 4) | l);
24096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    i += 2;
24196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
24296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                else if (ch == '+') {
24396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = ' ';
24496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
24596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                else {
24696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = bytes[i];
24796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
24896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                length++;
24996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
25096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return new String(bytes, 0, length, "UTF-8");
25196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
25296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        catch (UnsupportedEncodingException uee) {
25396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
25496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
25596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
25696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
25796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
25896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Returns true if the specified date is within today. Returns false otherwise.
25996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param date
26096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @return
26196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
26296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static boolean isDateToday(Date date) {
26396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // TODO But Calendar is so slowwwwwww....
26496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        Date today = new Date();
26596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (date.getYear() == today.getYear() &&
26696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                date.getMonth() == today.getMonth() &&
26796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                date.getDate() == today.getDate()) {
26896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return true;
26996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
27096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return false;
27196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
27296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
27396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /*
27496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * TODO disabled this method globally. It is used in all the settings screens but I just
27596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * noticed that an unrelated icon was dimmed. Android must share drawables internally.
27696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
27796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static void setCompoundDrawablesAlpha(TextView view, int alpha) {
27896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//        Drawable[] drawables = view.getCompoundDrawables();
27996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//        for (Drawable drawable : drawables) {
28096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//            if (drawable != null) {
28196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//                drawable.setAlpha(alpha);
28296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//            }
28396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//        }
28496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
285e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler
286e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler    // TODO: unit test this
287e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler    public static String buildMailboxIdSelection(ContentResolver resolver, long mailboxId) {
288e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        // Setup default selection & args, then add to it as necessary
289e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        StringBuilder selection = new StringBuilder(
2906c21942ec45f561d711b3d74ecca8e62afb735c4Andrew Stadler                MessageColumns.FLAG_LOADED + " IN ("
2916c21942ec45f561d711b3d74ecca8e62afb735c4Andrew Stadler                + Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE
2926c21942ec45f561d711b3d74ecca8e62afb735c4Andrew Stadler                + ") AND ");
293e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        if (mailboxId == Mailbox.QUERY_ALL_INBOXES
294e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            || mailboxId == Mailbox.QUERY_ALL_DRAFTS
295e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            || mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
296e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            // query for all mailboxes of type INBOX, DRAFTS, or OUTBOX
297e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            int type;
298e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
299e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                type = Mailbox.TYPE_INBOX;
300e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            } else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
301e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                type = Mailbox.TYPE_DRAFTS;
302e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            } else {
303e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                type = Mailbox.TYPE_OUTBOX;
304e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            }
305e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            StringBuilder inboxes = new StringBuilder();
306e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            Cursor c = resolver.query(Mailbox.CONTENT_URI,
307e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                        EmailContent.ID_PROJECTION,
308e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                        MailboxColumns.TYPE + "=? AND " + MailboxColumns.FLAG_VISIBLE + "=1",
309e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                        new String[] { Integer.toString(type) }, null);
310e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            // build an IN (mailboxId, ...) list
311e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            // TODO do this directly in the provider
312e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            while (c.moveToNext()) {
313e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                if (inboxes.length() != 0) {
314e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                    inboxes.append(",");
315e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                }
316e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                inboxes.append(c.getLong(EmailContent.ID_PROJECTION_COLUMN));
317e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            }
318e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            c.close();
319e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            selection.append(MessageColumns.MAILBOX_KEY + " IN ");
320e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            selection.append("(").append(inboxes).append(")");
321e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        } else  if (mailboxId == Mailbox.QUERY_ALL_UNREAD) {
322e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            selection.append(Message.FLAG_READ + "=0");
323e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        } else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
324e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            selection.append(Message.FLAG_FAVORITE + "=1");
325e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        } else {
326e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            selection.append(MessageColumns.MAILBOX_KEY + "=" + mailboxId);
327e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        }
328e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        return selection.toString();
329e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler    }
330dadba9949895696108b31124fc0c6aa1a297ab1csatok
331833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    // TODO When the UI is settled, cache all strings/drawables
332833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    // TODO When the UI is settled, write up tests
333833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    // TODO When the UI is settled, remove backward-compatibility methods
334dadba9949895696108b31124fc0c6aa1a297ab1csatok    public static class FolderProperties {
335dadba9949895696108b31124fc0c6aa1a297ab1csatok
336dadba9949895696108b31124fc0c6aa1a297ab1csatok        private static FolderProperties sInstance;
337dadba9949895696108b31124fc0c6aa1a297ab1csatok
338833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final Context mContext;
339833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
340dadba9949895696108b31124fc0c6aa1a297ab1csatok        // Caches for frequently accessed resources.
341833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final String[] mSpecialMailbox;
342833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final TypedArray mSpecialMailboxDrawable;
343833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final Drawable mDefaultMailboxDrawable;
344833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final Drawable mSummaryStarredMailboxDrawable;
345833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final Drawable mSummaryCombinedInboxDrawable;
346dadba9949895696108b31124fc0c6aa1a297ab1csatok
347dadba9949895696108b31124fc0c6aa1a297ab1csatok        private FolderProperties(Context context) {
348833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            mContext = context.getApplicationContext();
349fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler            mSpecialMailbox = context.getResources().getStringArray(R.array.mailbox_display_names);
350dadba9949895696108b31124fc0c6aa1a297ab1csatok            for (int i = 0; i < mSpecialMailbox.length; ++i) {
351dadba9949895696108b31124fc0c6aa1a297ab1csatok                if ("".equals(mSpecialMailbox[i])) {
352dadba9949895696108b31124fc0c6aa1a297ab1csatok                    // there is no localized name, so use the display name from the server
353dadba9949895696108b31124fc0c6aa1a297ab1csatok                    mSpecialMailbox[i] = null;
354dadba9949895696108b31124fc0c6aa1a297ab1csatok                }
355dadba9949895696108b31124fc0c6aa1a297ab1csatok            }
356dadba9949895696108b31124fc0c6aa1a297ab1csatok            mSpecialMailboxDrawable =
357fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                context.getResources().obtainTypedArray(R.array.mailbox_display_icons);
358dadba9949895696108b31124fc0c6aa1a297ab1csatok            mDefaultMailboxDrawable =
359dadba9949895696108b31124fc0c6aa1a297ab1csatok                context.getResources().getDrawable(R.drawable.ic_list_folder);
3603786cab2aa1776e92fc67af3931a76cd8b848b48satok            mSummaryStarredMailboxDrawable =
3613786cab2aa1776e92fc67af3931a76cd8b848b48satok                context.getResources().getDrawable(R.drawable.ic_list_starred);
3623786cab2aa1776e92fc67af3931a76cd8b848b48satok            mSummaryCombinedInboxDrawable =
3633786cab2aa1776e92fc67af3931a76cd8b848b48satok                context.getResources().getDrawable(R.drawable.ic_list_combined_inbox);
364dadba9949895696108b31124fc0c6aa1a297ab1csatok        }
365dadba9949895696108b31124fc0c6aa1a297ab1csatok
3661f2caa80957e92519258e212b5fd45fb6c168a73Makoto Onuki        public static synchronized FolderProperties getInstance(Context context) {
367dadba9949895696108b31124fc0c6aa1a297ab1csatok            if (sInstance == null) {
3681f2caa80957e92519258e212b5fd45fb6c168a73Makoto Onuki                sInstance = new FolderProperties(context);
369dadba9949895696108b31124fc0c6aa1a297ab1csatok            }
370dadba9949895696108b31124fc0c6aa1a297ab1csatok            return sInstance;
371dadba9949895696108b31124fc0c6aa1a297ab1csatok        }
372dadba9949895696108b31124fc0c6aa1a297ab1csatok
373833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        // For backward compatibility.
374833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public String getDisplayName(int type) {
375833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            return getDisplayName(type, -1);
376833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        }
377833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
378833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        // For backward compatibility.
379833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public Drawable getSummaryMailboxIconIds(long id) {
380833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            return getIcon(-1, id);
381833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        }
382833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
383833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public Drawable getIconIds(int type) {
384833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            return getIcon(type, -1);
385833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        }
386833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
387dadba9949895696108b31124fc0c6aa1a297ab1csatok        /**
388dadba9949895696108b31124fc0c6aa1a297ab1csatok         * Lookup names of localized special mailboxes
389dadba9949895696108b31124fc0c6aa1a297ab1csatok         */
390833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public String getDisplayName(int type, long mailboxId) {
391833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            // Special combined mailboxes
392833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            int resId = 0;
393833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
394833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            // Can't use long for switch!?
395833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
396833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                resId = R.string.account_folder_list_summary_inbox;
397833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
398833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                resId = R.string.account_folder_list_summary_starred;
399833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
400833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                resId = R.string.account_folder_list_summary_drafts;
401833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
402833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                resId = R.string.account_folder_list_summary_outbox;
403833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            }
404833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            if (resId != 0) {
405833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                return mContext.getString(resId);
406833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            }
407833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
408dadba9949895696108b31124fc0c6aa1a297ab1csatok            if (type < mSpecialMailbox.length) {
409dadba9949895696108b31124fc0c6aa1a297ab1csatok                return mSpecialMailbox[type];
410dadba9949895696108b31124fc0c6aa1a297ab1csatok            }
411dadba9949895696108b31124fc0c6aa1a297ab1csatok            return null;
412dadba9949895696108b31124fc0c6aa1a297ab1csatok        }
413dadba9949895696108b31124fc0c6aa1a297ab1csatok
414dadba9949895696108b31124fc0c6aa1a297ab1csatok        /**
415dadba9949895696108b31124fc0c6aa1a297ab1csatok         * Lookup icons of special mailboxes
416dadba9949895696108b31124fc0c6aa1a297ab1csatok         */
417833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public Drawable getIcon(int type, long mailboxId) {
418833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
4193786cab2aa1776e92fc67af3931a76cd8b848b48satok                return mSummaryCombinedInboxDrawable;
420833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
4213786cab2aa1776e92fc67af3931a76cd8b848b48satok                return mSummaryStarredMailboxDrawable;
422833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
4233786cab2aa1776e92fc67af3931a76cd8b848b48satok                return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_DRAFTS);
424833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
4253786cab2aa1776e92fc67af3931a76cd8b848b48satok                return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_OUTBOX);
4263786cab2aa1776e92fc67af3931a76cd8b848b48satok            }
427833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            if (0 <= type && type < mSpecialMailboxDrawable.length()) {
428833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                return mSpecialMailboxDrawable.getDrawable(type);
429833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            }
4303786cab2aa1776e92fc67af3931a76cd8b848b48satok            return mDefaultMailboxDrawable;
4313786cab2aa1776e92fc67af3931a76cd8b848b48satok        }
432dadba9949895696108b31124fc0c6aa1a297ab1csatok    }
433da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler
434da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    private final static String HOSTAUTH_WHERE_CREDENTIALS = HostAuthColumns.ADDRESS + " like ?"
435da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            + " and " + HostAuthColumns.LOGIN + " like ?"
436da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            + " and " + HostAuthColumns.PROTOCOL + " not like \"smtp\"";
437da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    private final static String ACCOUNT_WHERE_HOSTAUTH = AccountColumns.HOST_AUTH_KEY_RECV + "=?";
438da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler
439da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    /**
440da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * Look for an existing account with the same username & server
441da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     *
442da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * @param context a system context
443da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * @param allowAccountId this account Id will not trigger (when editing an existing account)
4449d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @param hostName the server's address
4459d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @param userLogin the user's login string
4469d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @result null = no matching account found.  Account = matching account
447da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     */
4489d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank    public static Account findExistingAccount(Context context, long allowAccountId,
4499d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank            String hostName, String userLogin) {
450da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        ContentResolver resolver = context.getContentResolver();
451da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        Cursor c = resolver.query(HostAuth.CONTENT_URI, HostAuth.ID_PROJECTION,
452da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                HOSTAUTH_WHERE_CREDENTIALS, new String[] { hostName, userLogin }, null);
453da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        try {
454da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            while (c.moveToNext()) {
455da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                long hostAuthId = c.getLong(HostAuth.ID_PROJECTION_COLUMN);
4569d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank                // Find account with matching hostauthrecv key, and return it
457da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                Cursor c2 = resolver.query(Account.CONTENT_URI, Account.ID_PROJECTION,
458da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        ACCOUNT_WHERE_HOSTAUTH, new String[] { Long.toString(hostAuthId) }, null);
459da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                try {
460da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    while (c2.moveToNext()) {
461da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        long accountId = c2.getLong(Account.ID_PROJECTION_COLUMN);
462da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        if (accountId != allowAccountId) {
463da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            Account account = Account.restoreAccountWithId(context, accountId);
464da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            if (account != null) {
4659d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank                                return account;
466da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            }
467da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        }
468da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    }
469da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                } finally {
470da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    c2.close();
471da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                }
472da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            }
473da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        } finally {
474da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            c.close();
475da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        }
476da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler
477da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        return null;
478da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    }
479eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler
480eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    /**
481eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler     * Generate a random message-id header for locally-generated messages.
482eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler     */
483eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    public static String generateMessageId() {
484eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        StringBuffer sb = new StringBuffer();
485eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append("<");
486eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        for (int i = 0; i < 24; i++) {
487eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler            sb.append(Integer.toString((int)(Math.random() * 35), 36));
488eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        }
489eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append(".");
490eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append(Long.toString(System.currentTimeMillis()));
491eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append("@email.android.com>");
492eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        return sb.toString();
493eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    }
494eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler
495989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
496989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a time in milliseconds from a date string that represents a date/time in GMT
497989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @param DateTime date string in format 20090211T180303Z (rfc2445, iCalendar).
498989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the time in milliseconds (since Jan 1, 1970)
499989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
500989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static long parseDateTimeToMillis(String date) {
501989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = parseDateTimeToCalendar(date);
502989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal.getTimeInMillis();
503989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
504989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda
505989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
506989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a GregorianCalendar from a date string that represents a date/time in GMT
507989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @param DateTime date string in format 20090211T180303Z (rfc2445, iCalendar).
508989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the GregorianCalendar
509989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
510989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static GregorianCalendar parseDateTimeToCalendar(String date) {
511989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
512989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(4, 6)) - 1, Integer.parseInt(date.substring(6, 8)),
513989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(9, 11)), Integer.parseInt(date.substring(11, 13)),
514989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(13, 15)));
515989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
516989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal;
517989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
518989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda
519989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
520989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a time in milliseconds from an email date string that represents a date/time in GMT
521989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @param Email style DateTime string in format 2010-02-23T16:00:00.000Z (ISO 8601, rfc3339)
522989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the time in milliseconds (since Jan 1, 1970)
523989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
524989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static long parseEmailDateTimeToMillis(String date) {
525989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
526989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(5, 7)) - 1, Integer.parseInt(date.substring(8, 10)),
527989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(11, 13)), Integer.parseInt(date.substring(14, 16)),
528989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(17, 19)));
529989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
530989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal.getTimeInMillis();
531989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
53220225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki
5337e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private static byte[] encode(Charset charset, String s) {
53420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        if (s == null) {
53520225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki            return null;
53620225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        }
5377e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        final ByteBuffer buffer = charset.encode(CharBuffer.wrap(s));
53820225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        final byte[] bytes = new byte[buffer.limit()];
53920225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        buffer.get(bytes);
54020225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        return bytes;
54120225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki    }
54288a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki
5437e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private static String decode(Charset charset, byte[] b) {
544fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        if (b == null) {
545fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki            return null;
546fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        }
5477e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        final CharBuffer cb = charset.decode(ByteBuffer.wrap(b));
548fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        return new String(cb.array(), 0, cb.length());
549fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki    }
550fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki
5517e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Converts a String to UTF-8 */
5527e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static byte[] toUtf8(String s) {
5537e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return encode(UTF_8, s);
5547e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
5557e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
5567e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Builds a String from UTF-8 bytes */
5577e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static String fromUtf8(byte[] b) {
5587e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return decode(UTF_8, b);
5597e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
5607e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
5617e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Converts a String to ASCII bytes */
5627e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static byte[] toAscii(String s) {
5637e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return encode(ASCII, s);
5647e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
5657e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
5667e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Builds a String from ASCII bytes */
5677e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static String fromAscii(byte[] b) {
5687e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return decode(ASCII, b);
5697e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
5707e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
57188a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    /**
57288a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki     * @return true if the input is the first (or only) byte in a UTF-8 character
57388a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki     */
57488a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    public static boolean isFirstUtf8Byte(byte b) {
57588a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki        // If the top 2 bits is '10', it's not a first byte.
57688a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki        return (b & 0xc0) != 0x80;
57788a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    }
578dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki
579dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    public static String byteToHex(int b) {
580dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        return byteToHex(new StringBuilder(), b).toString();
581dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    }
582dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki
583dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    public static StringBuilder byteToHex(StringBuilder sb, int b) {
584dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        b &= 0xFF;
585dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        sb.append("0123456789ABCDEF".charAt(b >> 4));
586dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        sb.append("0123456789ABCDEF".charAt(b & 0xF));
587dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        return sb;
588dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    }
589eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank
590eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank    public static String replaceBareLfWithCrlf(String str) {
591eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank        return str.replace("\r", "").replace("\n", "\r\n");
592eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank    }
59359cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki
59459cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    /**
59559cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * Cancel an {@link AsyncTask}.  If it's already running, it'll be interrupted.
59659cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     */
59759cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    public static void cancelTaskInterrupt(AsyncTask<?, ?, ?> task) {
59859cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        cancelTask(task, true);
59959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    }
60059cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki
60159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    /**
60259cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * Cancel an {@link AsyncTask}.
60359cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *
60459cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
60559cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *        task should be interrupted; otherwise, in-progress tasks are allowed
60659cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *        to complete.
60759cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     */
60859cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) {
60959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) {
61059cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki            task.cancel(mayInterruptIfRunning);
61159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        }
61259cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    }
613d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki
614d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    /**
615d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     * @return Device's unique ID if available.  null if the device has no unique ID.
616d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     */
617d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    public static String getConsistentDeviceId(Context context) {
618d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final String deviceId;
619d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        try {
620d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            TelephonyManager tm =
621d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                    (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
622d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            if (tm == null) {
623d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                return null;
624d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            }
625d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            deviceId = tm.getDeviceId();
626d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            if (deviceId == null) {
627d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                return null;
628d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            }
629d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        } catch (Exception e) {
630d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            Log.d(Email.LOG_TAG, "Error in TelephonyManager.getDeviceId(): " + e.getMessage());
631d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            return null;
632d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        }
633d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final MessageDigest sha;
634d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        try {
635d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            sha = MessageDigest.getInstance("SHA-1");
636d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        } catch (NoSuchAlgorithmException impossible) {
637d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            return null;
638d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        }
639d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        sha.update(Utility.toUtf8(deviceId));
640d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final int hash = getSmallHashFromSha1(sha.digest());
641d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        return Integer.toString(hash);
642d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    }
643d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki
644d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    /**
645d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     * @return a non-negative integer generated from 20 byte SHA-1 hash.
646d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     */
647d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    /* package for testing */ static int getSmallHashFromSha1(byte[] sha1) {
648d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final int offset = sha1[19] & 0xf; // SHA1 is 20 bytes.
649d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        return ((sha1[offset]  & 0x7f) << 24)
650d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 1] & 0xff) << 16)
651d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 2] & 0xff) << 8)
652d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 3] & 0xff));
653d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    }
654128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki
655128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    /**
656128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * Try to make a date MIME(RFC 2822/5322)-compliant.
657128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *
658128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * It fixes:
659128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * - "Thu, 10 Dec 09 15:08:08 GMT-0700" to "Thu, 10 Dec 09 15:08:08 -0700"
660128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *   (4 digit zone value can't be preceded by "GMT")
661128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *   We got a report saying eBay sends a date in this format
662128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     */
663128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    public static String cleanUpMimeDate(String date) {
664128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        if (TextUtils.isEmpty(date)) {
665128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki            return date;
666128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        }
667128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        date = DATE_CLEANUP_PATTERN_WRONG_TIMEZONE.matcher(date).replaceFirst("$1");
668128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        return date;
669128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    }
6707e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
6717e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static ByteArrayInputStream streamFromAsciiString(String ascii) {
6727e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return new ByteArrayInputStream(toAscii(ascii));
6737e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
6747e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki
6757e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki    /**
6767e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     * A thread safe way to show a Toast.  This method uses {@link Activity#runOnUiThread}, so it
6777e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     * can be called on any thread.
6787e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     *
6797e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     * @param activity Parent activity.
6807e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     * @param resId Resource ID of the message string.
6817e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     */
68291d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    public static void showToast(Activity activity, int resId) {
68391d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki        showToast(activity, activity.getResources().getString(resId));
68491d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    }
68591d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki
68691d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    /**
68791d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     * A thread safe way to show a Toast.  This method uses {@link Activity#runOnUiThread}, so it
68891d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     * can be called on any thread.
68991d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     *
69091d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     * @param activity Parent activity.
69191d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     * @param message Message to show.
69291d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     */
69391d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    public static void showToast(final Activity activity, final String message) {
6947e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki        activity.runOnUiThread(new Runnable() {
6957e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki            public void run() {
69691d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki                Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
6977e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki            }
6987e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki        });
6997e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki    }
7003f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki
7013f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    /**
7023f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki     * Run {@code r} on a worker thread.
7033f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki     */
7043f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    public static void runAsync(final Runnable r) {
7053f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        new AsyncTask<Void, Void, Void>() {
7063f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            @Override protected Void doInBackground(Void... params) {
7073f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                r.run();
7083f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                return null;
7093f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            }
7103f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        }.execute();
7113f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    }
71244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
71344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /**
71444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * Formats the given size as a String in bytes, kB, MB or GB.  Ex: 12,315,000 = 11 MB
71544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     */
71644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    public static String formatSize(Context context, long size) {
71744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        final Resources res = context.getResources();
71844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        final long KB = 1024;
71944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        final long MB = (KB * 1024);
72044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        final long GB  = (MB * 1024);
72144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
72244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        int resId;
72344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        int value;
72444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
72544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        if (size < KB) {
72644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            resId = R.plurals.message_view_attachment_bytes;
72744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            value = (int) size;
72844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        } else if (size < MB) {
72944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            resId = R.plurals.message_view_attachment_kilobytes;
73044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            value = (int) (size / KB);
73144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        } else if (size < GB) {
73244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            resId = R.plurals.message_view_attachment_megabytes;
73344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            value = (int) (size / MB);
73444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        } else {
73544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            resId = R.plurals.message_view_attachment_gigabytes;
73644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            value = (int) (size / GB);
73744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
73844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        return res.getQuantityString(resId, value, value);
73944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
74044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
74144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /**
74244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * Interface used in {@link #createUniqueFile} instead of {@link File#createNewFile()} to make
74344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * it testable.
74444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     */
74544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /* package */ interface NewFileCreator {
74644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        public static final NewFileCreator DEFAULT = new NewFileCreator() {
74744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                    @Override public boolean createNewFile(File f) throws IOException {
74844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                        return f.createNewFile();
74944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                    }
75044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        };
75144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        public boolean createNewFile(File f) throws IOException ;
75244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
75344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
75444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /**
75544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * Creates a new empty file with a unique name in the given directory by appending a hyphen and
75644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * a number to the given filename.
75744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     *
75844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * @return a new File object, or null if one could not be created
75944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     */
76044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    public static File createUniqueFile(File directory, String filename) throws IOException {
76144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        return createUniqueFileInternal(NewFileCreator.DEFAULT, directory, filename);
76244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
76344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
76444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /* package */ static File createUniqueFileInternal(NewFileCreator nfc,
76544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            File directory, String filename) throws IOException {
76644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        File file = new File(directory, filename);
76744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        if (nfc.createNewFile(file)) {
76844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            return file;
76944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
77044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        // Get the extension of the file, if any.
77144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        int index = filename.lastIndexOf('.');
77244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        String format;
77344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        if (index != -1) {
77444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            String name = filename.substring(0, index);
77544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            String extension = filename.substring(index);
77644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            format = name + "-%d" + extension;
77744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        } else {
77844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            format = filename + "-%d";
77944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
78044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
78144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        for (int i = 2; i < Integer.MAX_VALUE; i++) {
78244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            file = new File(directory, String.format(format, i));
78344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            if (nfc.createNewFile(file)) {
78444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                return file;
78544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            }
78644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
78744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        return null;
78844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
789bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki
790bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    /**
791bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     * @return a long in column {@code column} of the first result row, if the query returns at
792bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     * least 1 row.  Otherwise returns {@code defaultValue}.
793bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     */
794bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    public static Long getFirstRowLong(Context context, Uri uri, String[] projection,
795bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column,
796bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            Long defaultValue) {
797bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        Cursor c = context.getContentResolver().query(uri, projection, selection, selectionArgs,
798bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki                sortOrder);
799bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        try {
800bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            if (c.moveToFirst()) {
801bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki                return c.getLong(column);
802bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            }
803bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        } finally {
804bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            c.close();
805bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        }
806bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        return defaultValue;
807bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    }
808bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki
809bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    /**
810bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     * {@link #getFirstRowLong} with null as a default value.
811bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     */
812bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    public static Long getFirstRowLong(Context context, Uri uri, String[] projection,
813bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column) {
814bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        return getFirstRowLong(context, uri, projection, selection, selectionArgs,
815bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki                sortOrder, column, null);
816bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    }
817833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
818833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    /**
81936bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     * @return an integer in column {@code column} of the first result row, if the query returns at
82036bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     * least 1 row.  Otherwise returns {@code defaultValue}.
82136bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     */
82236bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    public static Integer getFirstRowInt(Context context, Uri uri, String[] projection,
82336bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column,
82436bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki            Integer defaultValue) {
82536bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki        Long longDefault = (defaultValue == null) ? null : defaultValue.longValue();
82636bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki        Long result = getFirstRowLong(context, uri, projection, selection, selectionArgs, sortOrder,
82736bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki                column, longDefault);
82836bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki        return (result == null) ? null : result.intValue();
82936bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    }
83036bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki
83136bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    /**
83236bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     * {@link #getFirstRowInt} with null as a default value.
83336bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     */
83436bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    public static Integer getFirstRowInt(Context context, Uri uri, String[] projection,
83536bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column) {
83636bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki        return getFirstRowInt(context, uri, projection, selection, selectionArgs,
83736bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki                sortOrder, column, null);
83836bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    }
83936bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki
84036bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    /**
841833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki     * A class used to restore ListView state (e.g. scroll position) when changing adapter.
842833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki     */
8437fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki    public static class ListStateSaver implements Parcelable {
844833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final Parcelable mState;
845833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
8467fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        private ListStateSaver(Parcel p) {
8477fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki            mState = p.readParcelable(null);
8487fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        }
8497fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki
850833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public ListStateSaver(AbsListView lv) {
851833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            mState = lv.onSaveInstanceState();
852833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        }
853833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
854833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public void restore(AbsListView lv) {
855833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            lv.onRestoreInstanceState(mState);
856833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        }
8577fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki
8587fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        @Override
8597fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        public int describeContents() {
8607fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki            return 0;
8617fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        }
8627fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki
8637fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        @Override
8647fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        public void writeToParcel(Parcel dest, int flags) {
8657fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki            dest.writeParcelable(mState, flags);
8667fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        }
8677fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki
8687fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        public static final Parcelable.Creator<ListStateSaver> CREATOR
8697fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                = new Parcelable.Creator<ListStateSaver>() {
8707fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                    public ListStateSaver createFromParcel(Parcel in) {
8717fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                        return new ListStateSaver(in);
8727fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                    }
8737fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki
8747fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                    public ListStateSaver[] newArray(int size) {
8757fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                        return new ListStateSaver[size];
8767fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                    }
8777fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                };
878833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    }
879533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki
8804dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank    public static boolean attachmentExists(Context context, Attachment attachment) {
8814dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        if (attachment == null) {
8824dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            return false;
8834dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        } else if (attachment.mContentBytes != null) {
8844dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            return true;
8854dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        } else if (TextUtils.isEmpty(attachment.mContentUri)) {
886567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki            Log.w(Email.LOG_TAG, "attachmentExists ContentUri null.");
887d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            return false;
888d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        }
889d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        if (Email.DEBUG) {
890d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            Log.d(Email.LOG_TAG, "attachmentExists URI=" + attachment.mContentUri);
891d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        }
892d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        try {
893567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki            Uri fileUri = Uri.parse(attachment.mContentUri);
894d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            try {
895567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                InputStream inStream = context.getContentResolver().openInputStream(fileUri);
896567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                try {
897567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                    inStream.close();
898567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                } catch (IOException e) {
899567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                    // Nothing to be done if can't close the stream
900567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                }
901567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                return true;
902567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki            } catch (FileNotFoundException e) {
903567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                return false;
904d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            }
905567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki        } catch (RuntimeException re) {
906567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki            Log.w(Email.LOG_TAG, "attachmentExists RuntimeException=" + re);
907d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            return false;
908d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        }
90909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
91009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
91109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
91209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Check whether the message with a given id has unloaded attachments.  If the message is
91309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * a forwarded message, we look instead at the messages's source for the attachments.  If the
91409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * message or forward source can't be found, we return false
91509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @param context the caller's context
91609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @param messageId the id of the message
91709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @return whether or not the message has unloaded attachments
91809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
91909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static boolean hasUnloadedAttachments(Context context, long messageId) {
92009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Message msg = Message.restoreMessageWithId(context, messageId);
92109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        if (msg == null) return false;
92209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Attachment[] atts = Attachment.restoreAttachmentsWithMessageId(context, messageId);
92309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        for (Attachment att: atts) {
9244dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            if (!attachmentExists(context, att)) {
92509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // If the attachment doesn't exist and isn't marked for download, we're in trouble
92609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // since the outbound message will be stuck indefinitely in the Outbox.  Instead,
92709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // we'll just delete the attachment and continue; this is far better than the
92809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // alternative.  In theory, this situation shouldn't be possible.
92909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                if ((att.mFlags & (Attachment.FLAG_DOWNLOAD_FORWARD |
93009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                        Attachment.FLAG_DOWNLOAD_USER_REQUEST)) == 0) {
93109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    Log.d(Email.LOG_TAG, "Unloaded attachment isn't marked for download: " +
93209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                            att.mFileName + ", #" + att.mId);
93309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    Attachment.delete(context, Attachment.CONTENT_URI, att.mId);
93409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                } else if (att.mContentUri != null) {
93509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    // In this case, the attachment file is gone from the cache; let's clear the
93609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    // contentUri; this should be a very unusual case
93709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    ContentValues cv = new ContentValues();
93809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    cv.putNull(AttachmentColumns.CONTENT_URI);
93909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    Attachment.update(context, Attachment.CONTENT_URI, att.mId, cv);
94009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                }
94109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                return true;
94209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            }
94309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        }
94409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return false;
94509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
94609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
94709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
94809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Convenience method wrapping calls to retrieve columns from a single row, via EmailProvider.
94909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * The arguments are exactly the same as to contentResolver.query().  Results are returned in
95009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * an array of Strings corresponding to the columns in the projection.  If the cursor has no
95109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * rows, null is returned.
95209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
95309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static String[] getRowColumns(Context context, Uri contentUri, String[] projection,
95409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            String selection, String[] selectionArgs) {
95509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        String[] values = new String[projection.length];
95609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        ContentResolver cr = context.getContentResolver();
95709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Cursor c = cr.query(contentUri, projection, selection, selectionArgs, null);
95809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        try {
95909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            if (c.moveToFirst()) {
96009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                for (int i = 0; i < projection.length; i++) {
96109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    values[i] = c.getString(i);
96209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                }
96309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            } else {
96409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                return null;
96509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            }
96609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        } finally {
96709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            c.close();
96809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        }
96909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return values;
97009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
97109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
97209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
97309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Convenience method for retrieving columns from a particular row in EmailProvider.
97409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Passed in here are a base uri (e.g. Message.CONTENT_URI), the unique id of a row, and
97509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * a projection.  This method calls the previous one with the appropriate URI.
97609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
97709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static String[] getRowColumns(Context context, Uri baseUri, long id,
97809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            String ... projection) {
97909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return getRowColumns(context, ContentUris.withAppendedId(baseUri, id), projection, null,
98009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                null);
98109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
98209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
98309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static boolean isExternalStorageMounted() {
98409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
98509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
986533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki    /**
987533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki     * STOPSHIP Remove this method
988533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki     * Toggle between portrait and landscape.  Developement use only.
989533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki     */
990533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki    public static void changeOrientation(Activity activity) {
991533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki        activity.setRequestedOrientation(
992533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki                (activity.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
993533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki                ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
994533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki                : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
995533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki    }
996f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
997f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    /**
998f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki     * Class that supports running any operation for each account.
999f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki     */
1000f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    public abstract static class ForEachAccount extends AsyncTask<Void, Void, Long[]> {
1001f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        private final Context mContext;
1002f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1003f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        public ForEachAccount(Context context) {
1004f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            mContext = context;
1005f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
1006f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1007f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        @Override
1008f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected final Long[] doInBackground(Void... params) {
1009f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            ArrayList<Long> ids = new ArrayList<Long>();
1010f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            Cursor c = mContext.getContentResolver().query(EmailContent.Account.CONTENT_URI,
1011f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                    EmailContent.Account.ID_PROJECTION, null, null, null);
1012f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            try {
1013f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                while (c.moveToNext()) {
1014f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                    ids.add(c.getLong(EmailContent.Account.ID_PROJECTION_COLUMN));
1015f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                }
1016f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            } finally {
1017f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                c.close();
1018f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            }
1019f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            return ids.toArray(EMPTY_LONGS);
1020f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
1021f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1022f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        @Override
1023f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected final void onPostExecute(Long[] ids) {
1024f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            if (ids != null && !isCancelled()) {
1025f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                for (long id : ids) {
1026f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                    performAction(id);
1027f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                }
1028f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            }
1029f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            onFinished();
1030f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
1031f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1032f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        /**
1033f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         * This method will be called for each account.
1034f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         */
1035f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected abstract void performAction(long accountId);
1036f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1037f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        /**
1038f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         * Called when the iteration is finished.
1039f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         */
1040f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected void onFinished() {
1041f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
1042f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    }
104396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
1044