Utility.java revision 07597e547bc02cd2247caa866d25b94745dcd448
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;
44082443978595bee3b7907563dc7665f908872e20Makoto Onukiimport android.os.Handler;
457fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onukiimport android.os.Parcel;
46833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onukiimport android.os.Parcelable;
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;
52082443978595bee3b7907563dc7665f908872e20Makoto Onukiimport android.widget.ListView;
53f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongkerimport android.widget.TextView;
547e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onukiimport android.widget.Toast;
55e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler
567e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.io.ByteArrayInputStream;
5744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onukiimport java.io.File;
58d755cdce13217d25ac33169b5e410709636255c4Makoto Onukiimport java.io.FileNotFoundException;
5996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.IOException;
6096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStream;
6196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStreamReader;
6296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.UnsupportedEncodingException;
6320225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.ByteBuffer;
6420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.CharBuffer;
6520225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.charset.Charset;
66697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onukiimport java.security.MessageDigest;
67d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onukiimport java.security.NoSuchAlgorithmException;
68f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onukiimport java.util.ArrayList;
69767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onukiimport java.util.Collection;
7096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.util.Date;
71989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Predaimport java.util.GregorianCalendar;
72989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Predaimport java.util.TimeZone;
73128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onukiimport java.util.regex.Pattern;
7496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
7596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpublic class Utility {
7620225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki    public static final Charset UTF_8 = Charset.forName("UTF-8");
777e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static final Charset ASCII = Charset.forName("US-ASCII");
787e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
797e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static final String[] EMPTY_STRINGS = new String[0];
80f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    public static final Long[] EMPTY_LONGS = new Long[0];
8120225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki
82128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    // "GMT" + "+" or "-" + 4 digits
83128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    private static final Pattern DATE_CLEANUP_PATTERN_WRONG_TIMEZONE =
84128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki            Pattern.compile("GMT([-+]\\d{4})$");
85128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki
867183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki    private static final String SELECTION_FLAG_LOADED_FOR_VISIBLE_MESSAGE =
877183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            " AND ("
887183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            + MessageColumns.FLAG_LOADED + " IN ("
897183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            + Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE
907183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            + "))";
917183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki
9296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public final static String readInputStream(InputStream in, String encoding) throws IOException {
9396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        InputStreamReader reader = new InputStreamReader(in, encoding);
9496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
9596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        int count;
9696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        char[] buf = new char[512];
9796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        while ((count = reader.read(buf)) != -1) {
9896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            sb.append(buf, 0, count);
9996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
10096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return sb.toString();
10196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
10296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
10396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public final static boolean arrayContains(Object[] a, Object o) {
10496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        for (int i = 0, count = a.length; i < count; i++) {
10596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (a[i].equals(o)) {
10696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                return true;
10796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
10896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
10996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return false;
11096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
11196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
11296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
11396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Combines the given array of Objects into a single string using the
11496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * seperator character and each Object's toString() method. between each
11596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * part.
11696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     *
11796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param parts
11896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param seperator
11996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @return
12096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
12196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String combine(Object[] parts, char seperator) {
12296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (parts == null) {
12396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
12496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
12596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
12696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        for (int i = 0; i < parts.length; i++) {
12796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            sb.append(parts[i].toString());
12896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (i < parts.length - 1) {
12996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                sb.append(seperator);
13096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
13196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
13296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return sb.toString();
13396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
13496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String base64Decode(String encoded) {
13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (encoded == null) {
13696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
13796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
138f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker        byte[] decoded = Base64.decode(encoded, Base64.DEFAULT);
13996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return new String(decoded);
14096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
14196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String base64Encode(String s) {
14396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (s == null) {
14496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return s;
14596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
146f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker        return Base64.encodeToString(s.getBytes(), Base64.NO_WRAP);
14796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
14896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
149e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank    public static boolean isTextViewNotEmpty(TextView view) {
150e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        return !TextUtils.isEmpty(view.getText());
15196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
15296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
153e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank    public static boolean isPortFieldValid(TextView view) {
154e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        CharSequence chars = view.getText();
155e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        if (TextUtils.isEmpty(chars)) return false;
156e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        Integer port;
157e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        // In theory, we can't get an illegal value here, since the field is monitored for valid
158e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        // numeric input. But this might be used elsewhere without such a check.
159e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        try {
160e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank            port = Integer.parseInt(chars.toString());
161e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        } catch (NumberFormatException e) {
162e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank            return false;
163e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        }
164e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        return port > 0 && port < 65536;
165e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     }
16696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
16796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
168e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     * Ensures that the given string starts and ends with the double quote character. The string is
169e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     * not modified in any way except to add the double quote character to start and end if it's not
170e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     * already there.
171f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
17296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * TODO: Rename this, because "quoteString()" can mean so many different things.
173f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
17496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * sample -> "sample"
17596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * "sample" -> "sample"
17696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * ""sample"" -> "sample"
17796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * "sample"" -> "sample"
17896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * sa"mp"le -> "sa"mp"le"
17996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * "sa"mp"le" -> "sa"mp"le"
18096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * (empty string) -> ""
18196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * " -> ""
18296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param s
18396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @return
18496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
18596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String quoteString(String s) {
18696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (s == null) {
18796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
18896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
18996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (!s.matches("^\".*\"$")) {
19096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return "\"" + s + "\"";
19196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
19296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        else {
19396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return s;
19496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
19596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
196f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
19796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
198f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     * Apply quoting rules per IMAP RFC,
19996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * quoted          = DQUOTE *QUOTED-CHAR DQUOTE
20096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * QUOTED-CHAR     = <any TEXT-CHAR except quoted-specials> / "\" quoted-specials
20196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * quoted-specials = DQUOTE / "\"
202f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
20396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * This is used primarily for IMAP login, but might be useful elsewhere.
204f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
20596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * NOTE:  Not very efficient - you may wish to preflight this, or perhaps it should check
20696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * for trouble chars before calling the replace functions.
207f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
20896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param s The string to be quoted.
20996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @return A copy of the string, having undergone quoting as described above
21096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
21196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String imapQuoted(String s) {
212f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
21396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // First, quote any backslashes by replacing \ with \\
21496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // regex Pattern:  \\    (Java string const = \\\\)
21596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // Substitute:     \\\\  (Java string const = \\\\\\\\)
21696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        String result = s.replaceAll("\\\\", "\\\\\\\\");
217f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
21896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // Then, quote any double-quotes by replacing " with \"
21996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // regex Pattern:  "    (Java string const = \")
22096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // Substitute:     \\"  (Java string const = \\\\\")
22196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        result = result.replaceAll("\"", "\\\\\"");
222f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
22396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // return string with quotes around it
22496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return "\"" + result + "\"";
22596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
226f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
22796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
22896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * A fast version of  URLDecoder.decode() that works only with UTF-8 and does only two
22996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * allocations. This version is around 3x as fast as the standard one and I'm using it
23096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * hundreds of times in places that slow down the UI, so it helps.
23196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
23296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String fastUrlDecode(String s) {
23396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
23496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            byte[] bytes = s.getBytes("UTF-8");
23596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            byte ch;
23696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            int length = 0;
23796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            for (int i = 0, count = bytes.length; i < count; i++) {
23896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                ch = bytes[i];
23996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                if (ch == '%') {
24096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    int h = (bytes[i + 1] - '0');
24196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    int l = (bytes[i + 2] - '0');
24296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    if (h > 9) {
24396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                        h -= 7;
24496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    }
24596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    if (l > 9) {
24696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                        l -= 7;
24796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    }
24896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = (byte) ((h << 4) | l);
24996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    i += 2;
25096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
25196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                else if (ch == '+') {
25296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = ' ';
25396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
25496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                else {
25596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = bytes[i];
25696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
25796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                length++;
25896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
25996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return new String(bytes, 0, length, "UTF-8");
26096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
26196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        catch (UnsupportedEncodingException uee) {
26296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
26396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
26496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
26596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
26696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
26796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Returns true if the specified date is within today. Returns false otherwise.
26896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param date
26996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @return
27096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
27196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static boolean isDateToday(Date date) {
27296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        // TODO But Calendar is so slowwwwwww....
27396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        Date today = new Date();
27496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (date.getYear() == today.getYear() &&
27596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                date.getMonth() == today.getMonth() &&
27696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                date.getDate() == today.getDate()) {
27796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return true;
27896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
27996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return false;
28096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
28196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
28296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /*
28396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * TODO disabled this method globally. It is used in all the settings screens but I just
28496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * noticed that an unrelated icon was dimmed. Android must share drawables internally.
28596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
28696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static void setCompoundDrawablesAlpha(TextView view, int alpha) {
28796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//        Drawable[] drawables = view.getCompoundDrawables();
28896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//        for (Drawable drawable : drawables) {
28996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//            if (drawable != null) {
29096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//                drawable.setAlpha(alpha);
29196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//            }
29296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//        }
29396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
294e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler
2957183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki    /**
2967183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki     * Returns the where clause for a message list selection.
2977183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki     *
2987183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki     * MUST NOT be called on the UI thread.
2997183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki     */
3007183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki    public static String buildMailboxIdSelection(Context context, long mailboxId) {
3017183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki        final ContentResolver resolver = context.getContentResolver();
3027183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki        final StringBuilder selection = new StringBuilder();
3037183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki
3047183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki        // We don't check "flagLoaded" for messages in Outbox.
3057183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki        boolean testFlagLoaded = true;
3067183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki
307e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        if (mailboxId == Mailbox.QUERY_ALL_INBOXES
308e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            || mailboxId == Mailbox.QUERY_ALL_DRAFTS
309e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            || mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
310e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            // query for all mailboxes of type INBOX, DRAFTS, or OUTBOX
311e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            int type;
312e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
313e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                type = Mailbox.TYPE_INBOX;
314e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            } else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
315e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                type = Mailbox.TYPE_DRAFTS;
316e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            } else {
317e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                type = Mailbox.TYPE_OUTBOX;
3187183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki                testFlagLoaded = false;
319e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            }
320e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            StringBuilder inboxes = new StringBuilder();
321e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            Cursor c = resolver.query(Mailbox.CONTENT_URI,
322e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                        EmailContent.ID_PROJECTION,
323e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                        MailboxColumns.TYPE + "=? AND " + MailboxColumns.FLAG_VISIBLE + "=1",
324e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                        new String[] { Integer.toString(type) }, null);
325e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            // build an IN (mailboxId, ...) list
326e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            while (c.moveToNext()) {
327e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                if (inboxes.length() != 0) {
328e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                    inboxes.append(",");
329e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                }
330e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler                inboxes.append(c.getLong(EmailContent.ID_PROJECTION_COLUMN));
331e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            }
332e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            c.close();
333e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            selection.append(MessageColumns.MAILBOX_KEY + " IN ");
334e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            selection.append("(").append(inboxes).append(")");
335e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        } else  if (mailboxId == Mailbox.QUERY_ALL_UNREAD) {
336e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            selection.append(Message.FLAG_READ + "=0");
337e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        } else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
338e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            selection.append(Message.FLAG_FAVORITE + "=1");
339e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        } else {
340e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler            selection.append(MessageColumns.MAILBOX_KEY + "=" + mailboxId);
3417183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            if (Mailbox.getMailboxType(context, mailboxId) == Mailbox.TYPE_OUTBOX) {
3427183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki                testFlagLoaded = false;
3437183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            }
344e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        }
3457183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki
3467183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki        if (testFlagLoaded) {
3477183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            // POP messages at the initial stage have very little information. (Server UID only)
3487183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            // This makes sure they're not visible in the message list.
3497183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            // This means unread counts on the mailbox list can be different from the
3507183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            // number of messages in the message list, but it should be transient...
3517183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki            selection.append(SELECTION_FLAG_LOADED_FOR_VISIBLE_MESSAGE);
3527183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki        }
3537183724276e0d829fd01a5bc1f2f6d0f6b6a8818Makoto Onuki
354e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler        return selection.toString();
355e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler    }
356dadba9949895696108b31124fc0c6aa1a297ab1csatok
357833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    // TODO When the UI is settled, cache all strings/drawables
358833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    // TODO When the UI is settled, write up tests
359833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    // TODO When the UI is settled, remove backward-compatibility methods
360dadba9949895696108b31124fc0c6aa1a297ab1csatok    public static class FolderProperties {
361dadba9949895696108b31124fc0c6aa1a297ab1csatok
362dadba9949895696108b31124fc0c6aa1a297ab1csatok        private static FolderProperties sInstance;
363dadba9949895696108b31124fc0c6aa1a297ab1csatok
364833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final Context mContext;
365833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
366dadba9949895696108b31124fc0c6aa1a297ab1csatok        // Caches for frequently accessed resources.
367833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final String[] mSpecialMailbox;
368833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final TypedArray mSpecialMailboxDrawable;
369833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final Drawable mDefaultMailboxDrawable;
370833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final Drawable mSummaryStarredMailboxDrawable;
371833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final Drawable mSummaryCombinedInboxDrawable;
372dadba9949895696108b31124fc0c6aa1a297ab1csatok
373dadba9949895696108b31124fc0c6aa1a297ab1csatok        private FolderProperties(Context context) {
374833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            mContext = context.getApplicationContext();
375fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler            mSpecialMailbox = context.getResources().getStringArray(R.array.mailbox_display_names);
376dadba9949895696108b31124fc0c6aa1a297ab1csatok            for (int i = 0; i < mSpecialMailbox.length; ++i) {
377dadba9949895696108b31124fc0c6aa1a297ab1csatok                if ("".equals(mSpecialMailbox[i])) {
378dadba9949895696108b31124fc0c6aa1a297ab1csatok                    // there is no localized name, so use the display name from the server
379dadba9949895696108b31124fc0c6aa1a297ab1csatok                    mSpecialMailbox[i] = null;
380dadba9949895696108b31124fc0c6aa1a297ab1csatok                }
381dadba9949895696108b31124fc0c6aa1a297ab1csatok            }
382dadba9949895696108b31124fc0c6aa1a297ab1csatok            mSpecialMailboxDrawable =
383fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                context.getResources().obtainTypedArray(R.array.mailbox_display_icons);
384dadba9949895696108b31124fc0c6aa1a297ab1csatok            mDefaultMailboxDrawable =
385dadba9949895696108b31124fc0c6aa1a297ab1csatok                context.getResources().getDrawable(R.drawable.ic_list_folder);
3863786cab2aa1776e92fc67af3931a76cd8b848b48satok            mSummaryStarredMailboxDrawable =
3873786cab2aa1776e92fc67af3931a76cd8b848b48satok                context.getResources().getDrawable(R.drawable.ic_list_starred);
3883786cab2aa1776e92fc67af3931a76cd8b848b48satok            mSummaryCombinedInboxDrawable =
3893786cab2aa1776e92fc67af3931a76cd8b848b48satok                context.getResources().getDrawable(R.drawable.ic_list_combined_inbox);
390dadba9949895696108b31124fc0c6aa1a297ab1csatok        }
391dadba9949895696108b31124fc0c6aa1a297ab1csatok
3921f2caa80957e92519258e212b5fd45fb6c168a73Makoto Onuki        public static synchronized FolderProperties getInstance(Context context) {
393dadba9949895696108b31124fc0c6aa1a297ab1csatok            if (sInstance == null) {
3941f2caa80957e92519258e212b5fd45fb6c168a73Makoto Onuki                sInstance = new FolderProperties(context);
395dadba9949895696108b31124fc0c6aa1a297ab1csatok            }
396dadba9949895696108b31124fc0c6aa1a297ab1csatok            return sInstance;
397dadba9949895696108b31124fc0c6aa1a297ab1csatok        }
398dadba9949895696108b31124fc0c6aa1a297ab1csatok
399833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        // For backward compatibility.
400833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public String getDisplayName(int type) {
401833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            return getDisplayName(type, -1);
402833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        }
403833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
404833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        // For backward compatibility.
405833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public Drawable getSummaryMailboxIconIds(long id) {
406833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            return getIcon(-1, id);
407833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        }
408833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
409833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public Drawable getIconIds(int type) {
410833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            return getIcon(type, -1);
411833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        }
412833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
413dadba9949895696108b31124fc0c6aa1a297ab1csatok        /**
414dadba9949895696108b31124fc0c6aa1a297ab1csatok         * Lookup names of localized special mailboxes
415dadba9949895696108b31124fc0c6aa1a297ab1csatok         */
416833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public String getDisplayName(int type, long mailboxId) {
417833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            // Special combined mailboxes
418833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            int resId = 0;
419833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
420833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            // Can't use long for switch!?
421833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
422833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                resId = R.string.account_folder_list_summary_inbox;
423833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
424833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                resId = R.string.account_folder_list_summary_starred;
425833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
426833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                resId = R.string.account_folder_list_summary_drafts;
427833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
428833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                resId = R.string.account_folder_list_summary_outbox;
429833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            }
430833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            if (resId != 0) {
431833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                return mContext.getString(resId);
432833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            }
433833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
434dadba9949895696108b31124fc0c6aa1a297ab1csatok            if (type < mSpecialMailbox.length) {
435dadba9949895696108b31124fc0c6aa1a297ab1csatok                return mSpecialMailbox[type];
436dadba9949895696108b31124fc0c6aa1a297ab1csatok            }
437dadba9949895696108b31124fc0c6aa1a297ab1csatok            return null;
438dadba9949895696108b31124fc0c6aa1a297ab1csatok        }
439dadba9949895696108b31124fc0c6aa1a297ab1csatok
440dadba9949895696108b31124fc0c6aa1a297ab1csatok        /**
441dadba9949895696108b31124fc0c6aa1a297ab1csatok         * Lookup icons of special mailboxes
442dadba9949895696108b31124fc0c6aa1a297ab1csatok         */
443833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public Drawable getIcon(int type, long mailboxId) {
444833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
4453786cab2aa1776e92fc67af3931a76cd8b848b48satok                return mSummaryCombinedInboxDrawable;
446833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
4473786cab2aa1776e92fc67af3931a76cd8b848b48satok                return mSummaryStarredMailboxDrawable;
448833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
4493786cab2aa1776e92fc67af3931a76cd8b848b48satok                return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_DRAFTS);
450833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            } else if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
4513786cab2aa1776e92fc67af3931a76cd8b848b48satok                return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_OUTBOX);
4523786cab2aa1776e92fc67af3931a76cd8b848b48satok            }
453833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            if (0 <= type && type < mSpecialMailboxDrawable.length()) {
454833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki                return mSpecialMailboxDrawable.getDrawable(type);
455833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            }
4563786cab2aa1776e92fc67af3931a76cd8b848b48satok            return mDefaultMailboxDrawable;
4573786cab2aa1776e92fc67af3931a76cd8b848b48satok        }
458dadba9949895696108b31124fc0c6aa1a297ab1csatok    }
459da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler
460da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    private final static String HOSTAUTH_WHERE_CREDENTIALS = HostAuthColumns.ADDRESS + " like ?"
461da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            + " and " + HostAuthColumns.LOGIN + " like ?"
462da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            + " and " + HostAuthColumns.PROTOCOL + " not like \"smtp\"";
463da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    private final static String ACCOUNT_WHERE_HOSTAUTH = AccountColumns.HOST_AUTH_KEY_RECV + "=?";
464da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler
465da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    /**
466da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * Look for an existing account with the same username & server
467da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     *
468da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * @param context a system context
469da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * @param allowAccountId this account Id will not trigger (when editing an existing account)
4709d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @param hostName the server's address
4719d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @param userLogin the user's login string
4729d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @result null = no matching account found.  Account = matching account
473da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     */
4749d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank    public static Account findExistingAccount(Context context, long allowAccountId,
4759d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank            String hostName, String userLogin) {
476da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        ContentResolver resolver = context.getContentResolver();
477da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        Cursor c = resolver.query(HostAuth.CONTENT_URI, HostAuth.ID_PROJECTION,
478da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                HOSTAUTH_WHERE_CREDENTIALS, new String[] { hostName, userLogin }, null);
479da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        try {
480da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            while (c.moveToNext()) {
481da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                long hostAuthId = c.getLong(HostAuth.ID_PROJECTION_COLUMN);
4829d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank                // Find account with matching hostauthrecv key, and return it
483da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                Cursor c2 = resolver.query(Account.CONTENT_URI, Account.ID_PROJECTION,
484da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        ACCOUNT_WHERE_HOSTAUTH, new String[] { Long.toString(hostAuthId) }, null);
485da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                try {
486da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    while (c2.moveToNext()) {
487da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        long accountId = c2.getLong(Account.ID_PROJECTION_COLUMN);
488da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        if (accountId != allowAccountId) {
489da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            Account account = Account.restoreAccountWithId(context, accountId);
490da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            if (account != null) {
4919d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank                                return account;
492da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            }
493da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        }
494da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    }
495da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                } finally {
496da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    c2.close();
497da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                }
498da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            }
499da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        } finally {
500da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            c.close();
501da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        }
502da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler
503da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        return null;
504da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    }
505eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler
506eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    /**
507eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler     * Generate a random message-id header for locally-generated messages.
508eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler     */
509eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    public static String generateMessageId() {
510eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        StringBuffer sb = new StringBuffer();
511eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append("<");
512eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        for (int i = 0; i < 24; i++) {
513eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler            sb.append(Integer.toString((int)(Math.random() * 35), 36));
514eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        }
515eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append(".");
516eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append(Long.toString(System.currentTimeMillis()));
517eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append("@email.android.com>");
518eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        return sb.toString();
519eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    }
520eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler
521989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
522989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a time in milliseconds from a date string that represents a date/time in GMT
523989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @param DateTime date string in format 20090211T180303Z (rfc2445, iCalendar).
524989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the time in milliseconds (since Jan 1, 1970)
525989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
526989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static long parseDateTimeToMillis(String date) {
527989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = parseDateTimeToCalendar(date);
528989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal.getTimeInMillis();
529989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
530989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda
531989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
532989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a GregorianCalendar from a date string that represents a date/time in GMT
533989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @param DateTime date string in format 20090211T180303Z (rfc2445, iCalendar).
534989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the GregorianCalendar
535989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
536989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static GregorianCalendar parseDateTimeToCalendar(String date) {
537989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
538989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(4, 6)) - 1, Integer.parseInt(date.substring(6, 8)),
539989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(9, 11)), Integer.parseInt(date.substring(11, 13)),
540989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(13, 15)));
541989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
542989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal;
543989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
544989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda
545989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
546989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a time in milliseconds from an email date string that represents a date/time in GMT
547989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @param Email style DateTime string in format 2010-02-23T16:00:00.000Z (ISO 8601, rfc3339)
548989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the time in milliseconds (since Jan 1, 1970)
549989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
550989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static long parseEmailDateTimeToMillis(String date) {
551989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
552989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(5, 7)) - 1, Integer.parseInt(date.substring(8, 10)),
553989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(11, 13)), Integer.parseInt(date.substring(14, 16)),
554989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(17, 19)));
555989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
556989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal.getTimeInMillis();
557989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
55820225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki
5597e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private static byte[] encode(Charset charset, String s) {
56020225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        if (s == null) {
56120225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki            return null;
56220225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        }
5637e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        final ByteBuffer buffer = charset.encode(CharBuffer.wrap(s));
56420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        final byte[] bytes = new byte[buffer.limit()];
56520225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        buffer.get(bytes);
56620225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        return bytes;
56720225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki    }
56888a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki
5697e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private static String decode(Charset charset, byte[] b) {
570fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        if (b == null) {
571fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki            return null;
572fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        }
5737e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        final CharBuffer cb = charset.decode(ByteBuffer.wrap(b));
574fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        return new String(cb.array(), 0, cb.length());
575fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki    }
576fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki
5777e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Converts a String to UTF-8 */
5787e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static byte[] toUtf8(String s) {
5797e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return encode(UTF_8, s);
5807e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
5817e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
5827e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Builds a String from UTF-8 bytes */
5837e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static String fromUtf8(byte[] b) {
5847e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return decode(UTF_8, b);
5857e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
5867e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
5877e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Converts a String to ASCII bytes */
5887e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static byte[] toAscii(String s) {
5897e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return encode(ASCII, s);
5907e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
5917e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
5927e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Builds a String from ASCII bytes */
5937e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static String fromAscii(byte[] b) {
5947e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return decode(ASCII, b);
5957e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
5967e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
59788a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    /**
59888a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki     * @return true if the input is the first (or only) byte in a UTF-8 character
59988a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki     */
60088a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    public static boolean isFirstUtf8Byte(byte b) {
60188a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki        // If the top 2 bits is '10', it's not a first byte.
60288a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki        return (b & 0xc0) != 0x80;
60388a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    }
604dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki
605dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    public static String byteToHex(int b) {
606dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        return byteToHex(new StringBuilder(), b).toString();
607dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    }
608dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki
609dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    public static StringBuilder byteToHex(StringBuilder sb, int b) {
610dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        b &= 0xFF;
611dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        sb.append("0123456789ABCDEF".charAt(b >> 4));
612dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        sb.append("0123456789ABCDEF".charAt(b & 0xF));
613dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        return sb;
614dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    }
615eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank
616eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank    public static String replaceBareLfWithCrlf(String str) {
617eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank        return str.replace("\r", "").replace("\n", "\r\n");
618eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank    }
61959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki
62059cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    /**
62159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * Cancel an {@link AsyncTask}.  If it's already running, it'll be interrupted.
62259cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     */
62359cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    public static void cancelTaskInterrupt(AsyncTask<?, ?, ?> task) {
62459cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        cancelTask(task, true);
62559cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    }
62659cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki
62759cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    /**
62859cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * Cancel an {@link AsyncTask}.
62959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *
63059cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
63159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *        task should be interrupted; otherwise, in-progress tasks are allowed
63259cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *        to complete.
63359cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     */
63459cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) {
63559cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) {
63659cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki            task.cancel(mayInterruptIfRunning);
63759cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        }
63859cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    }
639d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki
640d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    /**
641d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     * @return Device's unique ID if available.  null if the device has no unique ID.
642d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     */
643d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    public static String getConsistentDeviceId(Context context) {
644d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final String deviceId;
645d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        try {
646d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            TelephonyManager tm =
647d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                    (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
648d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            if (tm == null) {
649d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                return null;
650d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            }
651d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            deviceId = tm.getDeviceId();
652d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            if (deviceId == null) {
653d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                return null;
654d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            }
655d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        } catch (Exception e) {
656d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            Log.d(Email.LOG_TAG, "Error in TelephonyManager.getDeviceId(): " + e.getMessage());
657d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            return null;
658d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        }
659697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onuki        return getSmallHash(deviceId);
660697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onuki    }
661697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onuki
662697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onuki    /* package */ static String getSmallHash(final String value) {
663d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final MessageDigest sha;
664d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        try {
665d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            sha = MessageDigest.getInstance("SHA-1");
666d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        } catch (NoSuchAlgorithmException impossible) {
667d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            return null;
668d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        }
669697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onuki        sha.update(Utility.toUtf8(value));
670d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final int hash = getSmallHashFromSha1(sha.digest());
671d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        return Integer.toString(hash);
672d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    }
673d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki
674d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    /**
675d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     * @return a non-negative integer generated from 20 byte SHA-1 hash.
676d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     */
677d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    /* package for testing */ static int getSmallHashFromSha1(byte[] sha1) {
678d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final int offset = sha1[19] & 0xf; // SHA1 is 20 bytes.
679d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        return ((sha1[offset]  & 0x7f) << 24)
680d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 1] & 0xff) << 16)
681d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 2] & 0xff) << 8)
682d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 3] & 0xff));
683d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    }
684128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki
685128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    /**
686128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * Try to make a date MIME(RFC 2822/5322)-compliant.
687128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *
688128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * It fixes:
689128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * - "Thu, 10 Dec 09 15:08:08 GMT-0700" to "Thu, 10 Dec 09 15:08:08 -0700"
690128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *   (4 digit zone value can't be preceded by "GMT")
691128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *   We got a report saying eBay sends a date in this format
692128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     */
693128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    public static String cleanUpMimeDate(String date) {
694128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        if (TextUtils.isEmpty(date)) {
695128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki            return date;
696128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        }
697128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        date = DATE_CLEANUP_PATTERN_WRONG_TIMEZONE.matcher(date).replaceFirst("$1");
698128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        return date;
699128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    }
7007e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
7017e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static ByteArrayInputStream streamFromAsciiString(String ascii) {
7027e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return new ByteArrayInputStream(toAscii(ascii));
7037e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
7047e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki
7057e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki    /**
7067e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     * A thread safe way to show a Toast.  This method uses {@link Activity#runOnUiThread}, so it
7077e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     * can be called on any thread.
7087e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     *
7097e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     * @param activity Parent activity.
7107e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     * @param resId Resource ID of the message string.
7117e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     */
71291d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    public static void showToast(Activity activity, int resId) {
71391d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki        showToast(activity, activity.getResources().getString(resId));
71491d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    }
71591d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki
71691d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    /**
71791d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     * A thread safe way to show a Toast.  This method uses {@link Activity#runOnUiThread}, so it
71891d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     * can be called on any thread.
71991d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     *
72091d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     * @param activity Parent activity.
72191d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     * @param message Message to show.
72291d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     */
72391d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    public static void showToast(final Activity activity, final String message) {
7247e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki        activity.runOnUiThread(new Runnable() {
7257e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki            public void run() {
72691d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki                Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
7277e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki            }
7287e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki        });
7297e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki    }
7303f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki
7313f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    /**
732b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * Run {@code r} on a worker thread, returning the AsyncTask
733b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * @return the AsyncTask; this is primarily for use by unit tests, which require the
734b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * result of the task
7353f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki     */
736b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank    public static AsyncTask<Void, Void, Void> runAsync(final Runnable r) {
737b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank        return new AsyncTask<Void, Void, Void>() {
7383f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            @Override protected Void doInBackground(Void... params) {
7393f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                r.run();
7403f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                return null;
7413f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            }
7423f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        }.execute();
7433f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    }
74444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
74544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /**
74644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * Formats the given size as a String in bytes, kB, MB or GB.  Ex: 12,315,000 = 11 MB
74744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     */
74844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    public static String formatSize(Context context, long size) {
74944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        final Resources res = context.getResources();
75044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        final long KB = 1024;
75144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        final long MB = (KB * 1024);
75244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        final long GB  = (MB * 1024);
75344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
75444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        int resId;
75544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        int value;
75644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
75744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        if (size < KB) {
75844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            resId = R.plurals.message_view_attachment_bytes;
75944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            value = (int) size;
76044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        } else if (size < MB) {
76144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            resId = R.plurals.message_view_attachment_kilobytes;
76244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            value = (int) (size / KB);
76344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        } else if (size < GB) {
76444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            resId = R.plurals.message_view_attachment_megabytes;
76544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            value = (int) (size / MB);
76644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        } else {
76744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            resId = R.plurals.message_view_attachment_gigabytes;
76844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            value = (int) (size / GB);
76944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
77044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        return res.getQuantityString(resId, value, value);
77144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
77244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
77344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /**
77444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * Interface used in {@link #createUniqueFile} instead of {@link File#createNewFile()} to make
77544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * it testable.
77644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     */
77744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /* package */ interface NewFileCreator {
77844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        public static final NewFileCreator DEFAULT = new NewFileCreator() {
77944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                    @Override public boolean createNewFile(File f) throws IOException {
78044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                        return f.createNewFile();
78144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                    }
78244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        };
78344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        public boolean createNewFile(File f) throws IOException ;
78444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
78544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
78644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /**
78744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * Creates a new empty file with a unique name in the given directory by appending a hyphen and
78844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * a number to the given filename.
78944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     *
79044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * @return a new File object, or null if one could not be created
79144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     */
79244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    public static File createUniqueFile(File directory, String filename) throws IOException {
79344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        return createUniqueFileInternal(NewFileCreator.DEFAULT, directory, filename);
79444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
79544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
79644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /* package */ static File createUniqueFileInternal(NewFileCreator nfc,
79744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            File directory, String filename) throws IOException {
79844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        File file = new File(directory, filename);
79944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        if (nfc.createNewFile(file)) {
80044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            return file;
80144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
80244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        // Get the extension of the file, if any.
80344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        int index = filename.lastIndexOf('.');
80444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        String format;
80544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        if (index != -1) {
80644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            String name = filename.substring(0, index);
80744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            String extension = filename.substring(index);
80844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            format = name + "-%d" + extension;
80944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        } else {
81044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            format = filename + "-%d";
81144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
81244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
81344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        for (int i = 2; i < Integer.MAX_VALUE; i++) {
81444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            file = new File(directory, String.format(format, i));
81544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            if (nfc.createNewFile(file)) {
81644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                return file;
81744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            }
81844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
81944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        return null;
82044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
821bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki
82207597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public interface CursorGetter<T> {
82307597e547bc02cd2247caa866d25b94745dcd448Marc Blank        T get(Cursor cursor, int column);
82407597e547bc02cd2247caa866d25b94745dcd448Marc Blank    }
82507597e547bc02cd2247caa866d25b94745dcd448Marc Blank
82607597e547bc02cd2247caa866d25b94745dcd448Marc Blank    private static final CursorGetter<Long> LONG_GETTER = new CursorGetter<Long>() {
82707597e547bc02cd2247caa866d25b94745dcd448Marc Blank        public Long get(Cursor cursor, int column) {
82807597e547bc02cd2247caa866d25b94745dcd448Marc Blank            return cursor.getLong(column);
82907597e547bc02cd2247caa866d25b94745dcd448Marc Blank        }
83007597e547bc02cd2247caa866d25b94745dcd448Marc Blank    };
83107597e547bc02cd2247caa866d25b94745dcd448Marc Blank
83207597e547bc02cd2247caa866d25b94745dcd448Marc Blank    private static final CursorGetter<Integer> INT_GETTER = new CursorGetter<Integer>() {
83307597e547bc02cd2247caa866d25b94745dcd448Marc Blank        public Integer get(Cursor cursor, int column) {
83407597e547bc02cd2247caa866d25b94745dcd448Marc Blank            return cursor.getInt(column);
83507597e547bc02cd2247caa866d25b94745dcd448Marc Blank        }
83607597e547bc02cd2247caa866d25b94745dcd448Marc Blank    };
83707597e547bc02cd2247caa866d25b94745dcd448Marc Blank
83807597e547bc02cd2247caa866d25b94745dcd448Marc Blank    private static final CursorGetter<byte[]> BLOB_GETTER = new CursorGetter<byte[]>() {
83907597e547bc02cd2247caa866d25b94745dcd448Marc Blank        public byte[] get(Cursor cursor, int column) {
84007597e547bc02cd2247caa866d25b94745dcd448Marc Blank            return cursor.getBlob(column);
84107597e547bc02cd2247caa866d25b94745dcd448Marc Blank        }
84207597e547bc02cd2247caa866d25b94745dcd448Marc Blank    };
84307597e547bc02cd2247caa866d25b94745dcd448Marc Blank
844bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    /**
84507597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * @return a generic in column {@code column} of the first result row, if the query returns at
846bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     * least 1 row.  Otherwise returns {@code defaultValue}.
847bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     */
84807597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public static <T extends Object> T getFirstRowColumn(Context context, Uri uri,
84907597e547bc02cd2247caa866d25b94745dcd448Marc Blank            String[] projection, String selection, String[] selectionArgs, String sortOrder,
85007597e547bc02cd2247caa866d25b94745dcd448Marc Blank            int column, T defaultValue, CursorGetter<T> getter) {
85107597e547bc02cd2247caa866d25b94745dcd448Marc Blank        // Use PARAMETER_LIMIT to restrict the query to the single row we need
85207597e547bc02cd2247caa866d25b94745dcd448Marc Blank        uri = uri.buildUpon().appendQueryParameter(EmailContent.PARAMETER_LIMIT, "1").build();
853bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        Cursor c = context.getContentResolver().query(uri, projection, selection, selectionArgs,
854bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki                sortOrder);
855b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki        if (c != null) {
856b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            try {
857b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki                if (c.moveToFirst()) {
85807597e547bc02cd2247caa866d25b94745dcd448Marc Blank                    return getter.get(c, column);
859b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki                }
860b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            } finally {
861b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki                c.close();
862bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            }
863bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        }
864bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        return defaultValue;
865bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    }
866bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki
867bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    /**
86807597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for a Long with null as a default value.
869bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     */
870bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    public static Long getFirstRowLong(Context context, Uri uri, String[] projection,
871bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column) {
87207597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
87307597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, null, LONG_GETTER);
874bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    }
875833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
876833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    /**
87707597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for a Long with a provided default value.
87836bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     */
87907597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public static Long getFirstRowLong(Context context, Uri uri, String[] projection,
88036bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column,
88107597e547bc02cd2247caa866d25b94745dcd448Marc Blank            Long defaultValue) {
88207597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
88307597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, defaultValue, LONG_GETTER);
88436bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    }
88536bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki
88636bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    /**
88707597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for an Integer with null as a default value.
88836bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     */
88936bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    public static Integer getFirstRowInt(Context context, Uri uri, String[] projection,
89036bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column) {
89107597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
89207597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, null, INT_GETTER);
89307597e547bc02cd2247caa866d25b94745dcd448Marc Blank    }
89407597e547bc02cd2247caa866d25b94745dcd448Marc Blank
89507597e547bc02cd2247caa866d25b94745dcd448Marc Blank    /**
89607597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for an Integer with a provided default value.
89707597e547bc02cd2247caa866d25b94745dcd448Marc Blank     */
89807597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public static Integer getFirstRowInt(Context context, Uri uri, String[] projection,
89907597e547bc02cd2247caa866d25b94745dcd448Marc Blank            String selection, String[] selectionArgs, String sortOrder, int column,
90007597e547bc02cd2247caa866d25b94745dcd448Marc Blank            Integer defaultValue) {
90107597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
90207597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, defaultValue, INT_GETTER);
90336bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    }
90436bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki
90507597e547bc02cd2247caa866d25b94745dcd448Marc Blank    /**
90607597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for a byte array with null as a default value.
90707597e547bc02cd2247caa866d25b94745dcd448Marc Blank     */
908b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki    public static byte[] getFirstRowBlob(Context context, Uri uri, String[] projection,
909b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column,
910b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            byte[] defaultValue) {
91107597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs, sortOrder,
91207597e547bc02cd2247caa866d25b94745dcd448Marc Blank                column, defaultValue, BLOB_GETTER);
913b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki    }
914b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki
91536bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    /**
916833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki     * A class used to restore ListView state (e.g. scroll position) when changing adapter.
917833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki     */
9187fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki    public static class ListStateSaver implements Parcelable {
919833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        private final Parcelable mState;
920833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
9217fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        private ListStateSaver(Parcel p) {
9227fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki            mState = p.readParcelable(null);
9237fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        }
9247fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki
925833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public ListStateSaver(AbsListView lv) {
926833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            mState = lv.onSaveInstanceState();
927833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        }
928833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
929833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        public void restore(AbsListView lv) {
930833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki            lv.onRestoreInstanceState(mState);
931833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki        }
9327fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki
9337fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        @Override
9347fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        public int describeContents() {
9357fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki            return 0;
9367fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        }
9377fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki
9387fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        @Override
9397fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        public void writeToParcel(Parcel dest, int flags) {
9407fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki            dest.writeParcelable(mState, flags);
9417fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        }
9427fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki
9437fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki        public static final Parcelable.Creator<ListStateSaver> CREATOR
9447fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                = new Parcelable.Creator<ListStateSaver>() {
9457fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                    public ListStateSaver createFromParcel(Parcel in) {
9467fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                        return new ListStateSaver(in);
9477fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                    }
9487fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki
9497fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                    public ListStateSaver[] newArray(int size) {
9507fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                        return new ListStateSaver[size];
9517fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                    }
9527fd307212f076fab56f575988e5e5bfaf1abdcc3Makoto Onuki                };
953833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    }
954533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki
9554dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank    public static boolean attachmentExists(Context context, Attachment attachment) {
9564dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        if (attachment == null) {
9574dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            return false;
9584dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        } else if (attachment.mContentBytes != null) {
9594dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            return true;
9604dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        } else if (TextUtils.isEmpty(attachment.mContentUri)) {
961567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki            Log.w(Email.LOG_TAG, "attachmentExists ContentUri null.");
962d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            return false;
963d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        }
964d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        if (Email.DEBUG) {
965d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            Log.d(Email.LOG_TAG, "attachmentExists URI=" + attachment.mContentUri);
966d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        }
967d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        try {
968567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki            Uri fileUri = Uri.parse(attachment.mContentUri);
969d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            try {
970567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                InputStream inStream = context.getContentResolver().openInputStream(fileUri);
971567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                try {
972567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                    inStream.close();
973567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                } catch (IOException e) {
974567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                    // Nothing to be done if can't close the stream
975567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                }
976567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                return true;
977567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki            } catch (FileNotFoundException e) {
978567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                return false;
979d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            }
980567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki        } catch (RuntimeException re) {
981567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki            Log.w(Email.LOG_TAG, "attachmentExists RuntimeException=" + re);
982d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            return false;
983d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        }
98409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
98509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
98609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
98709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Check whether the message with a given id has unloaded attachments.  If the message is
98809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * a forwarded message, we look instead at the messages's source for the attachments.  If the
98909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * message or forward source can't be found, we return false
99009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @param context the caller's context
99109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @param messageId the id of the message
99209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @return whether or not the message has unloaded attachments
99309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
99409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static boolean hasUnloadedAttachments(Context context, long messageId) {
99509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Message msg = Message.restoreMessageWithId(context, messageId);
99609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        if (msg == null) return false;
99709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Attachment[] atts = Attachment.restoreAttachmentsWithMessageId(context, messageId);
99809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        for (Attachment att: atts) {
9994dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            if (!attachmentExists(context, att)) {
100009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // If the attachment doesn't exist and isn't marked for download, we're in trouble
100109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // since the outbound message will be stuck indefinitely in the Outbox.  Instead,
100209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // we'll just delete the attachment and continue; this is far better than the
100309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // alternative.  In theory, this situation shouldn't be possible.
100409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                if ((att.mFlags & (Attachment.FLAG_DOWNLOAD_FORWARD |
100509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                        Attachment.FLAG_DOWNLOAD_USER_REQUEST)) == 0) {
100609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    Log.d(Email.LOG_TAG, "Unloaded attachment isn't marked for download: " +
100709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                            att.mFileName + ", #" + att.mId);
100809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    Attachment.delete(context, Attachment.CONTENT_URI, att.mId);
100909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                } else if (att.mContentUri != null) {
101009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    // In this case, the attachment file is gone from the cache; let's clear the
101109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    // contentUri; this should be a very unusual case
101209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    ContentValues cv = new ContentValues();
101309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    cv.putNull(AttachmentColumns.CONTENT_URI);
101409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    Attachment.update(context, Attachment.CONTENT_URI, att.mId, cv);
101509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                }
101609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                return true;
101709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            }
101809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        }
101909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return false;
102009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
102109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
102209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
102309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Convenience method wrapping calls to retrieve columns from a single row, via EmailProvider.
102409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * The arguments are exactly the same as to contentResolver.query().  Results are returned in
102509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * an array of Strings corresponding to the columns in the projection.  If the cursor has no
102609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * rows, null is returned.
102709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
102809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static String[] getRowColumns(Context context, Uri contentUri, String[] projection,
102909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            String selection, String[] selectionArgs) {
103009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        String[] values = new String[projection.length];
103109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        ContentResolver cr = context.getContentResolver();
103209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Cursor c = cr.query(contentUri, projection, selection, selectionArgs, null);
103309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        try {
103409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            if (c.moveToFirst()) {
103509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                for (int i = 0; i < projection.length; i++) {
103609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    values[i] = c.getString(i);
103709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                }
103809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            } else {
103909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                return null;
104009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            }
104109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        } finally {
104209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            c.close();
104309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        }
104409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return values;
104509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
104609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
104709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
104809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Convenience method for retrieving columns from a particular row in EmailProvider.
104909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Passed in here are a base uri (e.g. Message.CONTENT_URI), the unique id of a row, and
105009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * a projection.  This method calls the previous one with the appropriate URI.
105109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
105209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static String[] getRowColumns(Context context, Uri baseUri, long id,
105309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            String ... projection) {
105409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return getRowColumns(context, ContentUris.withAppendedId(baseUri, id), projection, null,
105509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                null);
105609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
105709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
105809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static boolean isExternalStorageMounted() {
105909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
106009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
1061533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki    /**
1062533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki     * STOPSHIP Remove this method
1063533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki     * Toggle between portrait and landscape.  Developement use only.
1064533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki     */
1065533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki    public static void changeOrientation(Activity activity) {
1066533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki        activity.setRequestedOrientation(
1067533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki                (activity.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
1068533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki                ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
1069533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki                : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
1070533e0f5beb15659832b589c1d68f96e6ef1b7e40Makoto Onuki    }
1071f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1072f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    /**
1073f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki     * Class that supports running any operation for each account.
1074f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki     */
1075f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    public abstract static class ForEachAccount extends AsyncTask<Void, Void, Long[]> {
1076f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        private final Context mContext;
1077f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1078f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        public ForEachAccount(Context context) {
1079f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            mContext = context;
1080f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
1081f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1082f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        @Override
1083f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected final Long[] doInBackground(Void... params) {
1084f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            ArrayList<Long> ids = new ArrayList<Long>();
1085f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            Cursor c = mContext.getContentResolver().query(EmailContent.Account.CONTENT_URI,
1086f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                    EmailContent.Account.ID_PROJECTION, null, null, null);
1087f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            try {
1088f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                while (c.moveToNext()) {
1089f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                    ids.add(c.getLong(EmailContent.Account.ID_PROJECTION_COLUMN));
1090f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                }
1091f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            } finally {
1092f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                c.close();
1093f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            }
1094f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            return ids.toArray(EMPTY_LONGS);
1095f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
1096f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1097f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        @Override
1098f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected final void onPostExecute(Long[] ids) {
1099f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            if (ids != null && !isCancelled()) {
1100f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                for (long id : ids) {
1101f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                    performAction(id);
1102f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                }
1103f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            }
1104f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            onFinished();
1105f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
1106f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1107f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        /**
1108f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         * This method will be called for each account.
1109f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         */
1110f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected abstract void performAction(long accountId);
1111f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
1112f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        /**
1113f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         * Called when the iteration is finished.
1114f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         */
1115f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected void onFinished() {
1116f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
1117f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    }
1118767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki
1119767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki    public static long[] toPrimitiveLongArray(Collection<Long> collection) {
1120767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        final int size = collection.size();
1121767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        final long[] ret = new long[size];
1122767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        // Collection doesn't have get(i).  (Iterable doesn't have size())
1123767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        int i = 0;
1124767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        for (Long value : collection) {
1125767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki            ret[i++] = value;
1126767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        }
1127767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        return ret;
1128767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki    }
1129082443978595bee3b7907563dc7665f908872e20Makoto Onuki
1130082443978595bee3b7907563dc7665f908872e20Makoto Onuki    /**
1131082443978595bee3b7907563dc7665f908872e20Makoto Onuki     * Workaround for the {@link ListView#smoothScrollToPosition} randomly scroll the view bug
1132082443978595bee3b7907563dc7665f908872e20Makoto Onuki     * if it's called right after {@link ListView#setAdapter}.
1133082443978595bee3b7907563dc7665f908872e20Makoto Onuki     */
1134082443978595bee3b7907563dc7665f908872e20Makoto Onuki    public static void listViewSmoothScrollToPosition(final Activity activity,
1135082443978595bee3b7907563dc7665f908872e20Makoto Onuki            final ListView listView, final int position) {
1136082443978595bee3b7907563dc7665f908872e20Makoto Onuki        // Workarond: delay-call smoothScrollToPosition()
1137082443978595bee3b7907563dc7665f908872e20Makoto Onuki        new Handler().post(new Runnable() {
1138082443978595bee3b7907563dc7665f908872e20Makoto Onuki            @Override
1139082443978595bee3b7907563dc7665f908872e20Makoto Onuki            public void run() {
1140082443978595bee3b7907563dc7665f908872e20Makoto Onuki                if (activity.isFinishing()) {
1141082443978595bee3b7907563dc7665f908872e20Makoto Onuki                    return; // Activity being destroyed
1142082443978595bee3b7907563dc7665f908872e20Makoto Onuki                }
1143082443978595bee3b7907563dc7665f908872e20Makoto Onuki                listView.smoothScrollToPosition(position);
1144082443978595bee3b7907563dc7665f908872e20Makoto Onuki            }
1145082443978595bee3b7907563dc7665f908872e20Makoto Onuki        });
1146082443978595bee3b7907563dc7665f908872e20Makoto Onuki    }
114796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
1148