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
1731d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankpackage com.android.emailcommon.utility;
1896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
197e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onukiimport android.app.Activity;
20d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport android.app.Fragment;
21e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadlerimport android.content.ContentResolver;
2209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.ContentUris;
2309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.ContentValues;
24f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongkerimport android.content.Context;
25e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadlerimport android.database.Cursor;
26adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onukiimport android.database.CursorWrapper;
27a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onukiimport android.graphics.Typeface;
28bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onukiimport android.net.Uri;
2959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onukiimport android.os.AsyncTask;
3009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.os.Environment;
31082443978595bee3b7907563dc7665f908872e20Makoto Onukiimport android.os.Handler;
32f11295f3352f7c89fc92c73731e41b057e236969Makoto Onukiimport android.os.Looper;
3319b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onukiimport android.os.StrictMode;
34d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onukiimport android.provider.OpenableColumns;
35a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onukiimport android.text.Spannable;
36a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onukiimport android.text.SpannableString;
37a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onukiimport android.text.SpannableStringBuilder;
38128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onukiimport android.text.TextUtils;
39a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onukiimport android.text.style.StyleSpan;
406cec1104fe8863fce2ee86ff5145076e6c436a00Doug Zongkerimport android.util.Base64;
41d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onukiimport android.util.Log;
42082443978595bee3b7907563dc7665f908872e20Makoto Onukiimport android.widget.ListView;
43f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongkerimport android.widget.TextView;
447e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onukiimport android.widget.Toast;
45e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler
46190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.Logging;
47190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.Account;
48190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent;
49190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.AccountColumns;
50190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment;
51190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.AttachmentColumns;
52190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.HostAuthColumns;
53190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.MailboxColumns;
54190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
55bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport com.android.emailcommon.provider.EmailContent.MessageColumns;
56190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.HostAuth;
57190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.Mailbox;
58190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.ProviderUnavailableException;
59190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank
607e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.io.ByteArrayInputStream;
6144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onukiimport java.io.File;
62d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport java.io.FileDescriptor;
63d755cdce13217d25ac33169b5e410709636255c4Makoto Onukiimport java.io.FileNotFoundException;
6496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.IOException;
6596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStream;
6696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStreamReader;
67d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport java.io.PrintWriter;
68d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport java.io.StringWriter;
6996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.UnsupportedEncodingException;
70ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onukiimport java.net.URI;
71ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onukiimport java.net.URISyntaxException;
7220225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.ByteBuffer;
7320225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.CharBuffer;
7420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.charset.Charset;
75697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onukiimport java.security.MessageDigest;
76d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onukiimport java.security.NoSuchAlgorithmException;
77f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onukiimport java.util.ArrayList;
78767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onukiimport java.util.Collection;
79989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Predaimport java.util.GregorianCalendar;
804e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onukiimport java.util.HashSet;
814e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onukiimport java.util.Set;
82989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Predaimport java.util.TimeZone;
83128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onukiimport java.util.regex.Pattern;
8496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
8596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpublic class Utility {
8620225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki    public static final Charset UTF_8 = Charset.forName("UTF-8");
877e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static final Charset ASCII = Charset.forName("US-ASCII");
887e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
897e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static final String[] EMPTY_STRINGS = new String[0];
90f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    public static final Long[] EMPTY_LONGS = new Long[0];
9120225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki
92128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    // "GMT" + "+" or "-" + 4 digits
93128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    private static final Pattern DATE_CLEANUP_PATTERN_WRONG_TIMEZONE =
94128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki            Pattern.compile("GMT([-+]\\d{4})$");
95128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki
96f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    private static Handler sMainThreadHandler;
97f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki
98f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    /**
99f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     * @return a {@link Handler} tied to the main thread.
100f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     */
101f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    public static Handler getMainThreadHandler() {
102f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki        if (sMainThreadHandler == null) {
103f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki            // No need to synchronize -- it's okay to create an extra Handler, which will be used
104f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki            // only once and then thrown away.
105f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki            sMainThreadHandler = new Handler(Looper.getMainLooper());
106f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki        }
107f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki        return sMainThreadHandler;
108f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    }
109f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki
11096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public final static String readInputStream(InputStream in, String encoding) throws IOException {
11196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        InputStreamReader reader = new InputStreamReader(in, encoding);
11296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
11396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        int count;
11496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        char[] buf = new char[512];
11596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        while ((count = reader.read(buf)) != -1) {
11696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            sb.append(buf, 0, count);
11796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
11896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return sb.toString();
11996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
12096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
12196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public final static boolean arrayContains(Object[] a, Object o) {
122190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank        int index = arrayIndex(a, o);
123190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank        return (index >= 0);
124190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank    }
125190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank
126190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank    public final static int arrayIndex(Object[] a, Object o) {
12796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        for (int i = 0, count = a.length; i < count; i++) {
12896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (a[i].equals(o)) {
129190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank                return i;
13096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
13196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
132190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank        return -1;
13396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
13496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
136fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * Returns a concatenated string containing the output of every Object's
137fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * toString() method, each separated by the given separator character.
13896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
139fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy    public static String combine(Object[] parts, char separator) {
14096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (parts == null) {
14196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
14396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
14496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        for (int i = 0; i < parts.length; i++) {
14596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            sb.append(parts[i].toString());
14696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (i < parts.length - 1) {
147fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy                sb.append(separator);
14896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
14996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
15096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return sb.toString();
15196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
15296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String base64Decode(String encoded) {
15396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (encoded == null) {
15496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
15596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
156f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker        byte[] decoded = Base64.decode(encoded, Base64.DEFAULT);
15796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return new String(decoded);
15896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
15996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
16096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String base64Encode(String s) {
16196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (s == null) {
16296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return s;
16396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
164f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker        return Base64.encodeToString(s.getBytes(), Base64.NO_WRAP);
16596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
16696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
167e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank    public static boolean isTextViewNotEmpty(TextView view) {
168e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        return !TextUtils.isEmpty(view.getText());
16996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
17096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
171e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank    public static boolean isPortFieldValid(TextView view) {
172e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        CharSequence chars = view.getText();
173e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        if (TextUtils.isEmpty(chars)) return false;
174e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        Integer port;
175e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        // In theory, we can't get an illegal value here, since the field is monitored for valid
176e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        // numeric input. But this might be used elsewhere without such a check.
177e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        try {
178e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank            port = Integer.parseInt(chars.toString());
179e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        } catch (NumberFormatException e) {
180e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank            return false;
181e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        }
182e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        return port > 0 && port < 65536;
183ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    }
184ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki
185ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    /**
186ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki     * Validate a hostname name field.
187ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki     *
188ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki     * Because we just use the {@link URI} class for validation, it'll accept some invalid
189ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki     * host names, but it works well enough...
190ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki     */
191ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    public static boolean isServerNameValid(TextView view) {
192ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        return isServerNameValid(view.getText().toString());
193ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    }
194ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki
195ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    public static boolean isServerNameValid(String serverName) {
196ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        serverName = serverName.trim();
197ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        if (TextUtils.isEmpty(serverName)) {
198ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki            return false;
199ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        }
200ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        try {
201ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki            URI uri = new URI(
202ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    "http",
203ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    null,
204ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    serverName,
205ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    -1,
206ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    null, // path
207ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    null, // query
208ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    null);
209ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki            return true;
210ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        } catch (URISyntaxException e) {
211ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki            return false;
212ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        }
213ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    }
21496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
21596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
216e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     * Ensures that the given string starts and ends with the double quote character. The string is
217e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     * not modified in any way except to add the double quote character to start and end if it's not
218e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank     * already there.
219f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
22096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * TODO: Rename this, because "quoteString()" can mean so many different things.
221f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker     *
22296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * sample -> "sample"
22396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * "sample" -> "sample"
22496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * ""sample"" -> "sample"
22596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * "sample"" -> "sample"
22696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * sa"mp"le -> "sa"mp"le"
22796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * "sa"mp"le" -> "sa"mp"le"
22896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * (empty string) -> ""
22996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * " -> ""
23096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
23196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String quoteString(String s) {
23296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (s == null) {
23396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
23496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
23596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (!s.matches("^\".*\"$")) {
23696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return "\"" + s + "\"";
23796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
23896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        else {
23996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return s;
24096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
24196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
242f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker
24396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
24496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * A fast version of  URLDecoder.decode() that works only with UTF-8 and does only two
24596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * allocations. This version is around 3x as fast as the standard one and I'm using it
24696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * hundreds of times in places that slow down the UI, so it helps.
24796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
24896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String fastUrlDecode(String s) {
24996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
25096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            byte[] bytes = s.getBytes("UTF-8");
25196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            byte ch;
25296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            int length = 0;
25396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            for (int i = 0, count = bytes.length; i < count; i++) {
25496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                ch = bytes[i];
25596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                if (ch == '%') {
25696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    int h = (bytes[i + 1] - '0');
25796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    int l = (bytes[i + 2] - '0');
25896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    if (h > 9) {
25996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                        h -= 7;
26096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    }
26196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    if (l > 9) {
26296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                        l -= 7;
26396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    }
26496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = (byte) ((h << 4) | l);
26596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    i += 2;
26696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
26796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                else if (ch == '+') {
26896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = ' ';
26996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
27096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                else {
27196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = bytes[i];
27296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
27396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                length++;
27496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
27596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return new String(bytes, 0, length, "UTF-8");
27696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
27796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        catch (UnsupportedEncodingException uee) {
27896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
27996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
28096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
281da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    private final static String HOSTAUTH_WHERE_CREDENTIALS = HostAuthColumns.ADDRESS + " like ?"
2820ff0e155c51250bedc35b5b585003c8cb87f2244Marc Blank            + " and " + HostAuthColumns.LOGIN + " like ?  ESCAPE '\\'"
283da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            + " and " + HostAuthColumns.PROTOCOL + " not like \"smtp\"";
284da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    private final static String ACCOUNT_WHERE_HOSTAUTH = AccountColumns.HOST_AUTH_KEY_RECV + "=?";
285da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler
286da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    /**
287da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * Look for an existing account with the same username & server
288da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     *
289da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * @param context a system context
290da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * @param allowAccountId this account Id will not trigger (when editing an existing account)
2919d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @param hostName the server's address
2929d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @param userLogin the user's login string
2939d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @result null = no matching account found.  Account = matching account
294da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     */
2959d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank    public static Account findExistingAccount(Context context, long allowAccountId,
2969d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank            String hostName, String userLogin) {
297da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        ContentResolver resolver = context.getContentResolver();
2980ff0e155c51250bedc35b5b585003c8cb87f2244Marc Blank        String userName = userLogin.replace("_", "\\_");
299da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        Cursor c = resolver.query(HostAuth.CONTENT_URI, HostAuth.ID_PROJECTION,
3000ff0e155c51250bedc35b5b585003c8cb87f2244Marc Blank                HOSTAUTH_WHERE_CREDENTIALS, new String[] { hostName, userName }, null);
30174143e89d538ce74e8c95e04cee56dca9412a7bdMarc Blank        if (c == null) throw new ProviderUnavailableException();
302da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        try {
303da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            while (c.moveToNext()) {
304da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                long hostAuthId = c.getLong(HostAuth.ID_PROJECTION_COLUMN);
3059d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank                // Find account with matching hostauthrecv key, and return it
306da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                Cursor c2 = resolver.query(Account.CONTENT_URI, Account.ID_PROJECTION,
307da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        ACCOUNT_WHERE_HOSTAUTH, new String[] { Long.toString(hostAuthId) }, null);
308da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                try {
309da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    while (c2.moveToNext()) {
310da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        long accountId = c2.getLong(Account.ID_PROJECTION_COLUMN);
311da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        if (accountId != allowAccountId) {
312da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            Account account = Account.restoreAccountWithId(context, accountId);
313da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            if (account != null) {
3149d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank                                return account;
315da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            }
316da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        }
317da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    }
318da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                } finally {
319da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    c2.close();
320da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                }
321da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            }
322da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        } finally {
323da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            c.close();
324da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        }
325da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler
326da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        return null;
327da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    }
328eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler
329eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    /**
330eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler     * Generate a random message-id header for locally-generated messages.
331eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler     */
332eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    public static String generateMessageId() {
333eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        StringBuffer sb = new StringBuffer();
334eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append("<");
335eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        for (int i = 0; i < 24; i++) {
336eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler            sb.append(Integer.toString((int)(Math.random() * 35), 36));
337eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        }
338eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append(".");
339eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append(Long.toString(System.currentTimeMillis()));
340eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append("@email.android.com>");
341eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        return sb.toString();
342eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    }
343eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler
344989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
345989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a time in milliseconds from a date string that represents a date/time in GMT
346fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * @param date string in format 20090211T180303Z (rfc2445, iCalendar).
347989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the time in milliseconds (since Jan 1, 1970)
348989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
349989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static long parseDateTimeToMillis(String date) {
350989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = parseDateTimeToCalendar(date);
351989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal.getTimeInMillis();
352989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
353989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda
354989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
355989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a GregorianCalendar from a date string that represents a date/time in GMT
356fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * @param date string in format 20090211T180303Z (rfc2445, iCalendar).
357989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the GregorianCalendar
358989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
359989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static GregorianCalendar parseDateTimeToCalendar(String date) {
360989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
361989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(4, 6)) - 1, Integer.parseInt(date.substring(6, 8)),
362989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(9, 11)), Integer.parseInt(date.substring(11, 13)),
363989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(13, 15)));
364989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
365989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal;
366989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
367989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
368989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a time in milliseconds from an email date string that represents a date/time in GMT
369fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * @param date string in format 2010-02-23T16:00:00.000Z (ISO 8601, rfc3339)
370989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the time in milliseconds (since Jan 1, 1970)
371989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
372989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static long parseEmailDateTimeToMillis(String date) {
373989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
374989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(5, 7)) - 1, Integer.parseInt(date.substring(8, 10)),
375989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(11, 13)), Integer.parseInt(date.substring(14, 16)),
376989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(17, 19)));
377989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
378989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal.getTimeInMillis();
379989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
38020225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki
3817e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private static byte[] encode(Charset charset, String s) {
38220225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        if (s == null) {
38320225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki            return null;
38420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        }
3857e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        final ByteBuffer buffer = charset.encode(CharBuffer.wrap(s));
38620225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        final byte[] bytes = new byte[buffer.limit()];
38720225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        buffer.get(bytes);
38820225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        return bytes;
38920225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki    }
39088a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki
3917e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private static String decode(Charset charset, byte[] b) {
392fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        if (b == null) {
393fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki            return null;
394fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        }
3957e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        final CharBuffer cb = charset.decode(ByteBuffer.wrap(b));
396fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        return new String(cb.array(), 0, cb.length());
397fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki    }
398fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki
3997e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Converts a String to UTF-8 */
4007e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static byte[] toUtf8(String s) {
4017e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return encode(UTF_8, s);
4027e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
4037e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
4047e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Builds a String from UTF-8 bytes */
4057e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static String fromUtf8(byte[] b) {
4067e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return decode(UTF_8, b);
4077e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
4087e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
4097e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Converts a String to ASCII bytes */
4107e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static byte[] toAscii(String s) {
4117e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return encode(ASCII, s);
4127e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
4137e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
4147e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Builds a String from ASCII bytes */
4157e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static String fromAscii(byte[] b) {
4167e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return decode(ASCII, b);
4177e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
4187e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
41988a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    /**
42088a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki     * @return true if the input is the first (or only) byte in a UTF-8 character
42188a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki     */
42288a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    public static boolean isFirstUtf8Byte(byte b) {
42388a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki        // If the top 2 bits is '10', it's not a first byte.
42488a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki        return (b & 0xc0) != 0x80;
42588a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    }
426dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki
427dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    public static String byteToHex(int b) {
428dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        return byteToHex(new StringBuilder(), b).toString();
429dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    }
430dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki
431dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    public static StringBuilder byteToHex(StringBuilder sb, int b) {
432dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        b &= 0xFF;
433dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        sb.append("0123456789ABCDEF".charAt(b >> 4));
434dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        sb.append("0123456789ABCDEF".charAt(b & 0xF));
435dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        return sb;
436dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    }
437eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank
438eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank    public static String replaceBareLfWithCrlf(String str) {
439eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank        return str.replace("\r", "").replace("\n", "\r\n");
440eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank    }
44159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki
44259cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    /**
44359cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * Cancel an {@link AsyncTask}.  If it's already running, it'll be interrupted.
44459cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     */
44559cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    public static void cancelTaskInterrupt(AsyncTask<?, ?, ?> task) {
44659cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        cancelTask(task, true);
44759cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    }
44859cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki
44959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    /**
4504a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki     * Cancel an {@link EmailAsyncTask}.  If it's already running, it'll be interrupted.
4514a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki     */
4524a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki    public static void cancelTaskInterrupt(EmailAsyncTask<?, ?, ?> task) {
4534a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki        if (task != null) {
4544a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki            task.cancel(true);
4554a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki        }
4564a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki    }
4574a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki
4584a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki    /**
45959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * Cancel an {@link AsyncTask}.
46059cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *
46159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
46259cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *        task should be interrupted; otherwise, in-progress tasks are allowed
46359cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *        to complete.
46459cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     */
46559cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) {
46659cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) {
46759cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki            task.cancel(mayInterruptIfRunning);
46859cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        }
46959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    }
470d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki
4713a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank    public static String getSmallHash(final String value) {
472d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final MessageDigest sha;
473d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        try {
474d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            sha = MessageDigest.getInstance("SHA-1");
475d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        } catch (NoSuchAlgorithmException impossible) {
476d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            return null;
477d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        }
478697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onuki        sha.update(Utility.toUtf8(value));
479d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final int hash = getSmallHashFromSha1(sha.digest());
480d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        return Integer.toString(hash);
481d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    }
482d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki
483d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    /**
484d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     * @return a non-negative integer generated from 20 byte SHA-1 hash.
485d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     */
486d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    /* package for testing */ static int getSmallHashFromSha1(byte[] sha1) {
487d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final int offset = sha1[19] & 0xf; // SHA1 is 20 bytes.
488d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        return ((sha1[offset]  & 0x7f) << 24)
489d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 1] & 0xff) << 16)
490d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 2] & 0xff) << 8)
491d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 3] & 0xff));
492d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    }
493128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki
494128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    /**
495128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * Try to make a date MIME(RFC 2822/5322)-compliant.
496128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *
497128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * It fixes:
498128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * - "Thu, 10 Dec 09 15:08:08 GMT-0700" to "Thu, 10 Dec 09 15:08:08 -0700"
499128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *   (4 digit zone value can't be preceded by "GMT")
500128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *   We got a report saying eBay sends a date in this format
501128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     */
502128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    public static String cleanUpMimeDate(String date) {
503128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        if (TextUtils.isEmpty(date)) {
504128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki            return date;
505128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        }
506128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        date = DATE_CLEANUP_PATTERN_WRONG_TIMEZONE.matcher(date).replaceFirst("$1");
507128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        return date;
508128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    }
5097e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
5107e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static ByteArrayInputStream streamFromAsciiString(String ascii) {
5117e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return new ByteArrayInputStream(toAscii(ascii));
5127e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
5137e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki
5147e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki    /**
515f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     * A thread safe way to show a Toast.  Can be called from any thread.
5167e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     *
517f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     * @param context context
5187e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     * @param resId Resource ID of the message string.
5197e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     */
520f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    public static void showToast(Context context, int resId) {
521f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki        showToast(context, context.getResources().getString(resId));
52291d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    }
52391d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki
52491d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    /**
525f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     * A thread safe way to show a Toast.  Can be called from any thread.
52691d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     *
527f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     * @param context context
52891d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     * @param message Message to show.
52991d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     */
530f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    public static void showToast(final Context context, final String message) {
531f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki        getMainThreadHandler().post(new Runnable() {
5325701e0a555a5c263862156c1291aa13b06850425Todd Kennedy            @Override
5337e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki            public void run() {
534f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki                Toast.makeText(context, message, Toast.LENGTH_LONG).show();
5357e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki            }
5367e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki        });
5377e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki    }
5383f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki
5393f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    /**
540b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * Run {@code r} on a worker thread, returning the AsyncTask
541b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * @return the AsyncTask; this is primarily for use by unit tests, which require the
542b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * result of the task
543d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki     *
544d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki     * @deprecated use {@link EmailAsyncTask#runAsyncParallel} or
545d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki     *     {@link EmailAsyncTask#runAsyncSerial}
5463f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki     */
547d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki    @Deprecated
548b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank    public static AsyncTask<Void, Void, Void> runAsync(final Runnable r) {
549b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank        return new AsyncTask<Void, Void, Void>() {
5503f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            @Override protected Void doInBackground(Void... params) {
5513f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                r.run();
5523f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                return null;
5533f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            }
554d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
5553f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    }
55644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
55744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /**
55844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * Interface used in {@link #createUniqueFile} instead of {@link File#createNewFile()} to make
55944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * it testable.
56044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     */
56144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /* package */ interface NewFileCreator {
56244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        public static final NewFileCreator DEFAULT = new NewFileCreator() {
56344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                    @Override public boolean createNewFile(File f) throws IOException {
56444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                        return f.createNewFile();
56544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                    }
56644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        };
56744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        public boolean createNewFile(File f) throws IOException ;
56844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
56944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
57044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /**
57144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * Creates a new empty file with a unique name in the given directory by appending a hyphen and
57244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * a number to the given filename.
57344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     *
57444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * @return a new File object, or null if one could not be created
57544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     */
57644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    public static File createUniqueFile(File directory, String filename) throws IOException {
57744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        return createUniqueFileInternal(NewFileCreator.DEFAULT, directory, filename);
57844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
57944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
58044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /* package */ static File createUniqueFileInternal(NewFileCreator nfc,
58144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            File directory, String filename) throws IOException {
58244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        File file = new File(directory, filename);
58344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        if (nfc.createNewFile(file)) {
58444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            return file;
58544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
58644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        // Get the extension of the file, if any.
58744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        int index = filename.lastIndexOf('.');
58844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        String format;
58944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        if (index != -1) {
59044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            String name = filename.substring(0, index);
59144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            String extension = filename.substring(index);
59244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            format = name + "-%d" + extension;
59344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        } else {
59444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            format = filename + "-%d";
59544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
59644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
59744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        for (int i = 2; i < Integer.MAX_VALUE; i++) {
59844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            file = new File(directory, String.format(format, i));
59944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            if (nfc.createNewFile(file)) {
60044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                return file;
60144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            }
60244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
60344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        return null;
60444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
605bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki
60607597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public interface CursorGetter<T> {
60707597e547bc02cd2247caa866d25b94745dcd448Marc Blank        T get(Cursor cursor, int column);
60807597e547bc02cd2247caa866d25b94745dcd448Marc Blank    }
60907597e547bc02cd2247caa866d25b94745dcd448Marc Blank
61007597e547bc02cd2247caa866d25b94745dcd448Marc Blank    private static final CursorGetter<Long> LONG_GETTER = new CursorGetter<Long>() {
6115701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        @Override
61207597e547bc02cd2247caa866d25b94745dcd448Marc Blank        public Long get(Cursor cursor, int column) {
61307597e547bc02cd2247caa866d25b94745dcd448Marc Blank            return cursor.getLong(column);
61407597e547bc02cd2247caa866d25b94745dcd448Marc Blank        }
61507597e547bc02cd2247caa866d25b94745dcd448Marc Blank    };
61607597e547bc02cd2247caa866d25b94745dcd448Marc Blank
61707597e547bc02cd2247caa866d25b94745dcd448Marc Blank    private static final CursorGetter<Integer> INT_GETTER = new CursorGetter<Integer>() {
6185701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        @Override
61907597e547bc02cd2247caa866d25b94745dcd448Marc Blank        public Integer get(Cursor cursor, int column) {
62007597e547bc02cd2247caa866d25b94745dcd448Marc Blank            return cursor.getInt(column);
62107597e547bc02cd2247caa866d25b94745dcd448Marc Blank        }
62207597e547bc02cd2247caa866d25b94745dcd448Marc Blank    };
62307597e547bc02cd2247caa866d25b94745dcd448Marc Blank
624ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    private static final CursorGetter<String> STRING_GETTER = new CursorGetter<String>() {
6255701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        @Override
626ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki        public String get(Cursor cursor, int column) {
627ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki            return cursor.getString(column);
628ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki        }
629ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    };
630ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki
63107597e547bc02cd2247caa866d25b94745dcd448Marc Blank    private static final CursorGetter<byte[]> BLOB_GETTER = new CursorGetter<byte[]>() {
6325701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        @Override
63307597e547bc02cd2247caa866d25b94745dcd448Marc Blank        public byte[] get(Cursor cursor, int column) {
63407597e547bc02cd2247caa866d25b94745dcd448Marc Blank            return cursor.getBlob(column);
63507597e547bc02cd2247caa866d25b94745dcd448Marc Blank        }
63607597e547bc02cd2247caa866d25b94745dcd448Marc Blank    };
63707597e547bc02cd2247caa866d25b94745dcd448Marc Blank
638bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    /**
6397093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     * @return if {@code original} is to the EmailProvider, add "?limit=1".  Otherwise just returns
6407093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     * {@code original}.
6417093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     *
6427093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     * Other providers don't support the limit param.  Also, changing URI passed from other apps
6437093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     * can cause permission errors.
6447093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     */
6457093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki    /* package */ static Uri buildLimitOneUri(Uri original) {
6467093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki        if ("content".equals(original.getScheme()) &&
6477093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki                EmailContent.AUTHORITY.equals(original.getAuthority())) {
6487093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki            return EmailContent.uriWithLimit(original, 1);
6497093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki        }
6507093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki        return original;
6517093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki    }
6527093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki
6537093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki    /**
65407597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * @return a generic in column {@code column} of the first result row, if the query returns at
655bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     * least 1 row.  Otherwise returns {@code defaultValue}.
656bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     */
65707597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public static <T extends Object> T getFirstRowColumn(Context context, Uri uri,
65807597e547bc02cd2247caa866d25b94745dcd448Marc Blank            String[] projection, String selection, String[] selectionArgs, String sortOrder,
65907597e547bc02cd2247caa866d25b94745dcd448Marc Blank            int column, T defaultValue, CursorGetter<T> getter) {
66007597e547bc02cd2247caa866d25b94745dcd448Marc Blank        // Use PARAMETER_LIMIT to restrict the query to the single row we need
6617093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki        uri = buildLimitOneUri(uri);
662bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        Cursor c = context.getContentResolver().query(uri, projection, selection, selectionArgs,
663bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki                sortOrder);
664b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki        if (c != null) {
665b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            try {
666b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki                if (c.moveToFirst()) {
66707597e547bc02cd2247caa866d25b94745dcd448Marc Blank                    return getter.get(c, column);
668b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki                }
669b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            } finally {
670b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki                c.close();
671bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            }
672bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        }
673bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        return defaultValue;
674bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    }
675bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki
676bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    /**
67707597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for a Long with null as a default value.
678bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     */
679bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    public static Long getFirstRowLong(Context context, Uri uri, String[] projection,
680bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column) {
68107597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
68207597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, null, LONG_GETTER);
683bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    }
684833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
685833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    /**
68607597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for a Long with a provided default value.
68736bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     */
68807597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public static Long getFirstRowLong(Context context, Uri uri, String[] projection,
68936bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column,
69007597e547bc02cd2247caa866d25b94745dcd448Marc Blank            Long defaultValue) {
69107597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
69207597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, defaultValue, LONG_GETTER);
69336bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    }
69436bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki
69536bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    /**
69607597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for an Integer with null as a default value.
69736bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     */
69836bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    public static Integer getFirstRowInt(Context context, Uri uri, String[] projection,
69936bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column) {
70007597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
70107597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, null, INT_GETTER);
70207597e547bc02cd2247caa866d25b94745dcd448Marc Blank    }
70307597e547bc02cd2247caa866d25b94745dcd448Marc Blank
70407597e547bc02cd2247caa866d25b94745dcd448Marc Blank    /**
70507597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for an Integer with a provided default value.
70607597e547bc02cd2247caa866d25b94745dcd448Marc Blank     */
70707597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public static Integer getFirstRowInt(Context context, Uri uri, String[] projection,
70807597e547bc02cd2247caa866d25b94745dcd448Marc Blank            String selection, String[] selectionArgs, String sortOrder, int column,
70907597e547bc02cd2247caa866d25b94745dcd448Marc Blank            Integer defaultValue) {
71007597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
71107597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, defaultValue, INT_GETTER);
71236bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    }
71336bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki
71407597e547bc02cd2247caa866d25b94745dcd448Marc Blank    /**
715ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki     * {@link #getFirstRowColumn} for a String with null as a default value.
716ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki     */
717ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    public static String getFirstRowString(Context context, Uri uri, String[] projection,
718ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column) {
7190e00160074abffe03285b355dce8b6ae2a6e3138Makoto Onuki        return getFirstRowString(context, uri, projection, selection, selectionArgs, sortOrder,
7200e00160074abffe03285b355dce8b6ae2a6e3138Makoto Onuki                column, null);
721ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    }
722ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki
723ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    /**
724ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki     * {@link #getFirstRowColumn} for a String with a provided default value.
725ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki     */
726ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    public static String getFirstRowString(Context context, Uri uri, String[] projection,
727ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column,
728ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki            String defaultValue) {
729ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
730ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki                sortOrder, column, defaultValue, STRING_GETTER);
731ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    }
732ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki
733ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    /**
734ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki     * {@link #getFirstRowColumn} for a byte array with a provided default value.
73507597e547bc02cd2247caa866d25b94745dcd448Marc Blank     */
736b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki    public static byte[] getFirstRowBlob(Context context, Uri uri, String[] projection,
737b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column,
738b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            byte[] defaultValue) {
73907597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs, sortOrder,
74007597e547bc02cd2247caa866d25b94745dcd448Marc Blank                column, defaultValue, BLOB_GETTER);
741b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki    }
742b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki
7434dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank    public static boolean attachmentExists(Context context, Attachment attachment) {
7444dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        if (attachment == null) {
7454dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            return false;
7464dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        } else if (attachment.mContentBytes != null) {
7474dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            return true;
748bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        } else if (TextUtils.isEmpty(attachment.mContentUri)) {
749bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return false;
750bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
751bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        try {
752bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            Uri fileUri = Uri.parse(attachment.mContentUri);
753d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            try {
754bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                InputStream inStream = context.getContentResolver().openInputStream(fileUri);
755567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                try {
756bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    inStream.close();
757bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                } catch (IOException e) {
758bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    // Nothing to be done if can't close the stream
759567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                }
760bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                return true;
761bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            } catch (FileNotFoundException e) {
762567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                return false;
763d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            }
764bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        } catch (RuntimeException re) {
765bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            Log.w(Logging.LOG_TAG, "attachmentExists RuntimeException=" + re);
766bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return false;
767d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        }
76809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
76909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
77009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
77109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Check whether the message with a given id has unloaded attachments.  If the message is
77209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * a forwarded message, we look instead at the messages's source for the attachments.  If the
77309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * message or forward source can't be found, we return false
77409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @param context the caller's context
77509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @param messageId the id of the message
77609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @return whether or not the message has unloaded attachments
77709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
77809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static boolean hasUnloadedAttachments(Context context, long messageId) {
77909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Message msg = Message.restoreMessageWithId(context, messageId);
78009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        if (msg == null) return false;
78109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Attachment[] atts = Attachment.restoreAttachmentsWithMessageId(context, messageId);
78209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        for (Attachment att: atts) {
7834dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            if (!attachmentExists(context, att)) {
78409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // If the attachment doesn't exist and isn't marked for download, we're in trouble
78509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // since the outbound message will be stuck indefinitely in the Outbox.  Instead,
78609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // we'll just delete the attachment and continue; this is far better than the
78709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // alternative.  In theory, this situation shouldn't be possible.
78809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                if ((att.mFlags & (Attachment.FLAG_DOWNLOAD_FORWARD |
78909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                        Attachment.FLAG_DOWNLOAD_USER_REQUEST)) == 0) {
79031d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                    Log.d(Logging.LOG_TAG, "Unloaded attachment isn't marked for download: " +
79109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                            att.mFileName + ", #" + att.mId);
79209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    Attachment.delete(context, Attachment.CONTENT_URI, att.mId);
793bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                } else if (att.mContentUri != null) {
79409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    // In this case, the attachment file is gone from the cache; let's clear the
79509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    // contentUri; this should be a very unusual case
79609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    ContentValues cv = new ContentValues();
79709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    cv.putNull(AttachmentColumns.CONTENT_URI);
79809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    Attachment.update(context, Attachment.CONTENT_URI, att.mId, cv);
79909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                }
80009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                return true;
80109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            }
80209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        }
80309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return false;
80409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
80509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
80609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
80709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Convenience method wrapping calls to retrieve columns from a single row, via EmailProvider.
80809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * The arguments are exactly the same as to contentResolver.query().  Results are returned in
80909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * an array of Strings corresponding to the columns in the projection.  If the cursor has no
81009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * rows, null is returned.
81109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
81209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static String[] getRowColumns(Context context, Uri contentUri, String[] projection,
81309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            String selection, String[] selectionArgs) {
81409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        String[] values = new String[projection.length];
81509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        ContentResolver cr = context.getContentResolver();
81609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Cursor c = cr.query(contentUri, projection, selection, selectionArgs, null);
81709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        try {
81809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            if (c.moveToFirst()) {
81909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                for (int i = 0; i < projection.length; i++) {
82009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    values[i] = c.getString(i);
82109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                }
82209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            } else {
82309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                return null;
82409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            }
82509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        } finally {
82609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            c.close();
82709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        }
82809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return values;
82909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
83009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
83109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
83209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Convenience method for retrieving columns from a particular row in EmailProvider.
83309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Passed in here are a base uri (e.g. Message.CONTENT_URI), the unique id of a row, and
83409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * a projection.  This method calls the previous one with the appropriate URI.
83509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
83609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static String[] getRowColumns(Context context, Uri baseUri, long id,
83709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            String ... projection) {
83809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return getRowColumns(context, ContentUris.withAppendedId(baseUri, id), projection, null,
83909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                null);
84009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
84109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
84209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static boolean isExternalStorageMounted() {
84309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
84409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
845f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
846f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    /**
847f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki     * Class that supports running any operation for each account.
848f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki     */
849f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    public abstract static class ForEachAccount extends AsyncTask<Void, Void, Long[]> {
850f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        private final Context mContext;
851f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
852f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        public ForEachAccount(Context context) {
853f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            mContext = context;
854f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
855f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
856f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        @Override
857f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected final Long[] doInBackground(Void... params) {
858f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            ArrayList<Long> ids = new ArrayList<Long>();
859f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank            Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
860f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank                    Account.ID_PROJECTION, null, null, null);
861f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            try {
862f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                while (c.moveToNext()) {
863f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank                    ids.add(c.getLong(Account.ID_PROJECTION_COLUMN));
864f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                }
865f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            } finally {
866f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                c.close();
867f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            }
868f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            return ids.toArray(EMPTY_LONGS);
869f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
870f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
871f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        @Override
872f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected final void onPostExecute(Long[] ids) {
873f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            if (ids != null && !isCancelled()) {
874f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                for (long id : ids) {
875f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                    performAction(id);
876f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                }
877f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            }
878f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            onFinished();
879f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
880f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
881f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        /**
882f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         * This method will be called for each account.
883f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         */
884f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected abstract void performAction(long accountId);
885f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
886f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        /**
887f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         * Called when the iteration is finished.
888f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         */
889f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected void onFinished() {
890f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
891f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    }
892767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki
893a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy    /**
894a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy     * Updates the last seen message key in the mailbox data base for the INBOX of the currently
895a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy     * shown account. If the account is {@link Account#ACCOUNT_ID_COMBINED_VIEW}, the INBOX for
896a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy     * all accounts are updated.
897a17ee57c7ff7eb3391cc32f700aaa4fed8aa971fTodd Kennedy     * @return an {@link EmailAsyncTask} for test only.
898a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy     */
899f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public static EmailAsyncTask<Void, Void, Void> updateLastNotifiedMessageKey(
900f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            final Context context, final long mailboxId) {
901a17ee57c7ff7eb3391cc32f700aaa4fed8aa971fTodd Kennedy        return EmailAsyncTask.runAsyncParallel(new Runnable() {
902f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            private void updateLastSeenMessageKeyForMailbox(long mailboxId) {
903a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                ContentResolver resolver = context.getContentResolver();
904f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
905a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    Cursor c = resolver.query(
906f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                            Mailbox.CONTENT_URI, EmailContent.ID_PROJECTION, Mailbox.TYPE + "=?",
907f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                            new String[] { Integer.toString(Mailbox.TYPE_INBOX) }, null);
908a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    if (c == null) throw new ProviderUnavailableException();
909a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    try {
910a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                        while (c.moveToNext()) {
911a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                            final long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
912f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                            updateLastSeenMessageKeyForMailbox(id);
913a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                        }
914a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    } finally {
915a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                        c.close();
916a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    }
917f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                } else if (mailboxId > 0L) {
918f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
919f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                   // mailbox has been removed
920a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    if (mailbox == null) {
921a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                        return;
922a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    }
923a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    // We use the highest _id for the account the mailbox table as the "last seen
924a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    // message key". We don't care if the message has been read or not. We only
925a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    // need a point at which we can compare against in the future. By setting this
926a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    // value, we are claiming that every message before this has potentially been
927a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                    // seen by the user.
928f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    long mostRecentMessageId = Utility.getFirstRowLong(context,
929f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                            ContentUris.withAppendedId(
930f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                                    EmailContent.MAILBOX_MOST_RECENT_MESSAGE_URI, mailboxId),
931f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                            Message.ID_COLUMN_PROJECTION, null, null, null,
932f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                            Message.ID_MAILBOX_COLUMN_ID, -1L);
933f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    long lastNotifiedMessageId = mailbox.mLastNotifiedMessageKey;
93483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    // Only update the db if the value has changed
935f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    if (mostRecentMessageId != lastNotifiedMessageId) {
936f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                        Log.d(Logging.LOG_TAG, "Most recent = " + mostRecentMessageId +
937f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                                ", last notified: " + lastNotifiedMessageId +
938f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                                "; updating last notified");
93983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                        ContentValues values = mailbox.toContentValues();
940f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                        values.put(MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY, mostRecentMessageId);
94183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                        resolver.update(
94283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                                Mailbox.CONTENT_URI,
94383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                                values,
94483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                                EmailContent.ID_SELECTION,
94583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                                new String[] { Long.toString(mailbox.mId) });
946f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    } else {
947f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                        Log.d(Logging.LOG_TAG, "Most recent = last notified; no change");
94883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    }
949a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy                }
950a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy            }
951a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy
952a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy            @Override
953a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy            public void run() {
954f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                updateLastSeenMessageKeyForMailbox(mailboxId);
955a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy            }
956a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy        });
957a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy    }
958a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy
959767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki    public static long[] toPrimitiveLongArray(Collection<Long> collection) {
9604e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        // Need to do this manually because we're converting to a primitive long array, not
9614e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        // a Long array.
962767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        final int size = collection.size();
963767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        final long[] ret = new long[size];
964767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        // Collection doesn't have get(i).  (Iterable doesn't have size())
965767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        int i = 0;
966767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        for (Long value : collection) {
967767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki            ret[i++] = value;
968767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        }
969767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        return ret;
970767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki    }
971082443978595bee3b7907563dc7665f908872e20Makoto Onuki
9724e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki    public static Set<Long> toLongSet(long[] array) {
9734e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        // Need to do this manually because we're converting from a primitive long array, not
9744e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        // a Long array.
9754e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        final int size = array.length;
9764e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        HashSet<Long> ret = new HashSet<Long>(size);
9774e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        for (int i = 0; i < size; i++) {
9784e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki            ret.add(array[i]);
9794e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        }
9804e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        return ret;
9814e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki    }
9824e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki
983082443978595bee3b7907563dc7665f908872e20Makoto Onuki    /**
984082443978595bee3b7907563dc7665f908872e20Makoto Onuki     * Workaround for the {@link ListView#smoothScrollToPosition} randomly scroll the view bug
985082443978595bee3b7907563dc7665f908872e20Makoto Onuki     * if it's called right after {@link ListView#setAdapter}.
986082443978595bee3b7907563dc7665f908872e20Makoto Onuki     */
987082443978595bee3b7907563dc7665f908872e20Makoto Onuki    public static void listViewSmoothScrollToPosition(final Activity activity,
988082443978595bee3b7907563dc7665f908872e20Makoto Onuki            final ListView listView, final int position) {
989082443978595bee3b7907563dc7665f908872e20Makoto Onuki        // Workarond: delay-call smoothScrollToPosition()
990082443978595bee3b7907563dc7665f908872e20Makoto Onuki        new Handler().post(new Runnable() {
991082443978595bee3b7907563dc7665f908872e20Makoto Onuki            @Override
992082443978595bee3b7907563dc7665f908872e20Makoto Onuki            public void run() {
993082443978595bee3b7907563dc7665f908872e20Makoto Onuki                if (activity.isFinishing()) {
994082443978595bee3b7907563dc7665f908872e20Makoto Onuki                    return; // Activity being destroyed
995082443978595bee3b7907563dc7665f908872e20Makoto Onuki                }
996082443978595bee3b7907563dc7665f908872e20Makoto Onuki                listView.smoothScrollToPosition(position);
997082443978595bee3b7907563dc7665f908872e20Makoto Onuki            }
998082443978595bee3b7907563dc7665f908872e20Makoto Onuki        });
999082443978595bee3b7907563dc7665f908872e20Makoto Onuki    }
1000d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki
1001d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    private static final String[] ATTACHMENT_META_NAME_PROJECTION = {
1002d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki        OpenableColumns.DISPLAY_NAME
1003d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    };
1004d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    private static final int ATTACHMENT_META_NAME_COLUMN_DISPLAY_NAME = 0;
1005d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki
1006d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    /**
1007d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki     * @return Filename of a content of {@code contentUri}.  If the provider doesn't provide the
1008d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki     * filename, returns the last path segment of the URI.
1009d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki     */
1010d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    public static String getContentFileName(Context context, Uri contentUri) {
1011d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki        String name = getFirstRowString(context, contentUri, ATTACHMENT_META_NAME_PROJECTION, null,
1012d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki                null, null, ATTACHMENT_META_NAME_COLUMN_DISPLAY_NAME);
1013d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki        if (name == null) {
1014d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki            name = contentUri.getLastPathSegment();
1015d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki        }
1016d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki        return name;
1017d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    }
1018adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1019adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    /**
1020a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki     * Append a bold span to a {@link SpannableStringBuilder}.
1021a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki     */
1022a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki    public static SpannableStringBuilder appendBold(SpannableStringBuilder ssb, String text) {
1023a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki        if (!TextUtils.isEmpty(text)) {
1024a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki            SpannableString ss = new SpannableString(text);
1025a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki            ss.setSpan(new StyleSpan(Typeface.BOLD), 0, ss.length(),
1026a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1027a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki            ssb.append(ss);
1028a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki        }
1029a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki
1030a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki        return ssb;
1031a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki    }
1032a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki
1033a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki    /**
1034adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     * Stringify a cursor for logging purpose.
1035adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     */
1036adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    public static String dumpCursor(Cursor c) {
1037adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        StringBuilder sb = new StringBuilder();
1038adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        sb.append("[");
1039adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        while (c != null) {
1040adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            sb.append(c.getClass()); // Class name may not be available if toString() is overridden
1041adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            sb.append("/");
1042adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            sb.append(c.toString());
1043adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            if (c.isClosed()) {
1044adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                sb.append(" (closed)");
1045adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            }
1046adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            if (c instanceof CursorWrapper) {
1047adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                c = ((CursorWrapper) c).getWrappedCursor();
1048adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                sb.append(", ");
1049adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            } else {
1050adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                break;
1051adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            }
1052adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1053adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        sb.append("]");
1054adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        return sb.toString();
1055adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    }
1056adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1057adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    /**
1058adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     * Cursor wrapper that remembers where it was closed.
1059adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     *
1060adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     * Use {@link #get} to create a wrapped cursor.
1061adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     * USe {@link #getTraceIfAvailable} to get the stack trace.
1062adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     * Use {@link #log} to log if/where it was closed.
1063adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     */
1064adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    public static class CloseTraceCursorWrapper extends CursorWrapper {
106580a2e9109daa5d13cd748305b4c5f83578f33728Makoto Onuki        private static final boolean TRACE_ENABLED = false;
1066adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1067adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        private Exception mTrace;
1068adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1069adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        private CloseTraceCursorWrapper(Cursor cursor) {
1070adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            super(cursor);
1071adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1072adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1073adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        @Override
1074adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        public void close() {
1075adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            mTrace = new Exception("STACK TRACE");
1076adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            super.close();
1077adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1078adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1079adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        public static Exception getTraceIfAvailable(Cursor c) {
1080adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            if (c instanceof CloseTraceCursorWrapper) {
1081adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                return ((CloseTraceCursorWrapper) c).mTrace;
1082adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            } else {
1083adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                return null;
1084adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            }
1085adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1086adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1087adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        public static void log(Cursor c) {
1088adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            if (c == null) {
1089adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                return;
1090adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            }
1091adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            if (c.isClosed()) {
109231d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.w(Logging.LOG_TAG, "Cursor was closed here: Cursor=" + c,
109331d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                        getTraceIfAvailable(c));
1094adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            } else {
109531d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.w(Logging.LOG_TAG, "Cursor not closed.  Cursor=" + c);
1096adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            }
1097adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1098adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1099adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        public static Cursor get(Cursor original) {
1100adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            return TRACE_ENABLED ? new CloseTraceCursorWrapper(original) : original;
1101adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1102adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1103adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        /* package */ static CloseTraceCursorWrapper alwaysCreateForTest(Cursor original) {
1104adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            return new CloseTraceCursorWrapper(original);
1105adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1106adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    }
11070c75f83f03b3ef8e54163cba2d67ba086cf8af58Makoto Onuki
11080c75f83f03b3ef8e54163cba2d67ba086cf8af58Makoto Onuki    /**
1109fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * Test that the given strings are equal in a null-pointer safe fashion.
1110fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     */
1111fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy    public static boolean areStringsEqual(String s1, String s2) {
1112fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy        return (s1 != null && s1.equals(s2)) || (s1 == null && s2 == null);
1113fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy    }
111419b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki
111519b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki    public static void enableStrictMode(boolean enabled) {
111619b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki        StrictMode.setThreadPolicy(enabled
111719b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki                ? new StrictMode.ThreadPolicy.Builder().detectAll().build()
111819b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki                : StrictMode.ThreadPolicy.LAX);
111919b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki        StrictMode.setVmPolicy(enabled
112019b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki                ? new StrictMode.VmPolicy.Builder().detectAll().build()
112119b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki                : StrictMode.VmPolicy.LAX);
112219b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki    }
1123d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki
1124d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki    public static String dumpFragment(Fragment f) {
1125d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki        StringWriter sw = new StringWriter();
1126d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki        PrintWriter w = new PrintWriter(sw);
1127d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki        f.dump("", new FileDescriptor(), w, new String[0]);
1128d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki        return sw.toString();
1129d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki    }
11308de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki
11318de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki    /**
11328de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki     * Builds an "in" expression for SQLite.
11338de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki     *
11348de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki     * e.g. "ID" + 1,2,3 -> "ID in (1,2,3)".  If {@code values} is empty or null, it returns an
11358de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki     * empty string.
11368de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki     */
11378de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki    public static String buildInSelection(String columnName, Collection<? extends Number> values) {
11388de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        if ((values == null) || (values.size() == 0)) {
11398de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki            return "";
11408de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        }
11418de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        StringBuilder sb = new StringBuilder();
11428de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        sb.append(columnName);
11438de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        sb.append(" in (");
11448de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        String sep = "";
11458de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        for (Number n : values) {
11468de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki            sb.append(sep);
11478de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki            sb.append(n.toString());
11488de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki            sep = ",";
11498de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        }
11508de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        sb.append(')');
11518de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        return sb.toString();
11528de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki    }
1153bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
1154bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
1155bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Updates the last seen message key in the mailbox data base for the INBOX of the currently
1156bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * shown account. If the account is {@link Account#ACCOUNT_ID_COMBINED_VIEW}, the INBOX for
1157bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * all accounts are updated.
1158bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @return an {@link EmailAsyncTask} for test only.
1159bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     */
1160bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public static EmailAsyncTask<Void, Void, Void> updateLastSeenMessageKey(final Context context,
1161bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            final long accountId) {
1162bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return EmailAsyncTask.runAsyncParallel(new Runnable() {
1163bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            private void updateLastSeenMessageKeyForAccount(long accountId) {
1164bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                ContentResolver resolver = context.getContentResolver();
1165bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
1166bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    Cursor c = resolver.query(
1167bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            Account.CONTENT_URI, EmailContent.ID_PROJECTION, null, null, null);
1168bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    if (c == null) throw new ProviderUnavailableException();
1169bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    try {
1170bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        while (c.moveToNext()) {
1171bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            final long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
1172bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            updateLastSeenMessageKeyForAccount(id);
1173bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        }
1174bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    } finally {
1175bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        c.close();
1176bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    }
1177bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                } else if (accountId > 0L) {
1178bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    Mailbox mailbox =
1179bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_INBOX);
1180bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
1181bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    // mailbox has been removed
1182bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    if (mailbox == null) {
1183bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        return;
1184bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    }
1185bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    // We use the highest _id for the account the mailbox table as the "last seen
1186bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    // message key". We don't care if the message has been read or not. We only
1187bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    // need a point at which we can compare against in the future. By setting this
1188bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    // value, we are claiming that every message before this has potentially been
1189bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    // seen by the user.
1190bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    long messageId = Utility.getFirstRowLong(
1191bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            context,
1192bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            Message.CONTENT_URI,
1193bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            EmailContent.ID_PROJECTION,
1194bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            MessageColumns.MAILBOX_KEY + "=?",
1195bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            new String[] { Long.toString(mailbox.mId) },
1196bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            MessageColumns.ID + " DESC",
1197bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            EmailContent.ID_PROJECTION_COLUMN, 0L);
1198bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    long oldLastSeenMessageId = Utility.getFirstRowLong(
1199bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            context, ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailbox.mId),
1200bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            new String[] { MailboxColumns.LAST_SEEN_MESSAGE_KEY },
1201bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            null, null, null, 0, 0L);
1202bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    // Only update the db if the value has changed
1203bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    if (messageId != oldLastSeenMessageId) {
1204bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        ContentValues values = mailbox.toContentValues();
1205bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        values.put(MailboxColumns.LAST_SEEN_MESSAGE_KEY, messageId);
1206bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        resolver.update(
1207bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                                Mailbox.CONTENT_URI,
1208bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                                values,
1209bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                                EmailContent.ID_SELECTION,
1210bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                                new String[] { Long.toString(mailbox.mId) });
1211bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    }
1212bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                }
1213bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
1214bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
1215bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            @Override
1216bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            public void run() {
1217bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                updateLastSeenMessageKeyForAccount(accountId);
1218bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
1219bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        });
1220bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
122196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
1222