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;
41082443978595bee3b7907563dc7665f908872e20Makoto Onukiimport android.widget.ListView;
42f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongkerimport android.widget.TextView;
437e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onukiimport android.widget.Toast;
44e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler
45190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.Logging;
46190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.Account;
47190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent;
48190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.AccountColumns;
49190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment;
50190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.AttachmentColumns;
51190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.HostAuthColumns;
52190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
53190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.HostAuth;
54190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.ProviderUnavailableException;
555a3aebbd2dd8cdd4d7c1a76ce3085cd6a314c0d0Paul Westbrookimport com.android.mail.utils.LogUtils;
56190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank
577e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.io.ByteArrayInputStream;
5844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onukiimport java.io.File;
59d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport java.io.FileDescriptor;
60d755cdce13217d25ac33169b5e410709636255c4Makoto Onukiimport java.io.FileNotFoundException;
6196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.IOException;
6296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStream;
6396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStreamReader;
64d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport java.io.PrintWriter;
65d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport java.io.StringWriter;
6696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.UnsupportedEncodingException;
67ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onukiimport java.net.URI;
68ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onukiimport java.net.URISyntaxException;
6920225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.ByteBuffer;
7020225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.CharBuffer;
7120225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.charset.Charset;
72697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onukiimport java.security.MessageDigest;
73d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onukiimport java.security.NoSuchAlgorithmException;
74f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onukiimport java.util.ArrayList;
75767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onukiimport java.util.Collection;
76989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Predaimport java.util.GregorianCalendar;
774e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onukiimport java.util.HashSet;
784e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onukiimport java.util.Set;
79989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Predaimport java.util.TimeZone;
80128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onukiimport java.util.regex.Pattern;
8196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
8296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpublic class Utility {
8320225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki    public static final Charset UTF_8 = Charset.forName("UTF-8");
847e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static final Charset ASCII = Charset.forName("US-ASCII");
857e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
867e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static final String[] EMPTY_STRINGS = new String[0];
87f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    public static final Long[] EMPTY_LONGS = new Long[0];
8820225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki
89128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    // "GMT" + "+" or "-" + 4 digits
90128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    private static final Pattern DATE_CLEANUP_PATTERN_WRONG_TIMEZONE =
91128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki            Pattern.compile("GMT([-+]\\d{4})$");
92128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki
93f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    private static Handler sMainThreadHandler;
94f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki
95f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    /**
96f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     * @return a {@link Handler} tied to the main thread.
97f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     */
98f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    public static Handler getMainThreadHandler() {
99f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki        if (sMainThreadHandler == null) {
100f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki            // No need to synchronize -- it's okay to create an extra Handler, which will be used
101f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki            // only once and then thrown away.
102f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki            sMainThreadHandler = new Handler(Looper.getMainLooper());
103f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki        }
104f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki        return sMainThreadHandler;
105f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    }
106f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki
10796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public final static String readInputStream(InputStream in, String encoding) throws IOException {
10896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        InputStreamReader reader = new InputStreamReader(in, encoding);
10996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
11096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        int count;
11196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        char[] buf = new char[512];
11296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        while ((count = reader.read(buf)) != -1) {
11396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            sb.append(buf, 0, count);
11496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
11596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return sb.toString();
11696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
11796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
11896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public final static boolean arrayContains(Object[] a, Object o) {
119190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank        int index = arrayIndex(a, o);
120190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank        return (index >= 0);
121190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank    }
122190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank
123190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank    public final static int arrayIndex(Object[] a, Object o) {
12496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        for (int i = 0, count = a.length; i < count; i++) {
12596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (a[i].equals(o)) {
126190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank                return i;
12796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
12896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
129190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank        return -1;
13096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
13196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
13296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
133fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * Returns a concatenated string containing the output of every Object's
134fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * toString() method, each separated by the given separator character.
13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
136fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy    public static String combine(Object[] parts, char separator) {
13796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (parts == null) {
13896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
13996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
14096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
14196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        for (int i = 0; i < parts.length; i++) {
14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            sb.append(parts[i].toString());
14396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (i < parts.length - 1) {
144fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy                sb.append(separator);
14596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
14696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
14796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return sb.toString();
14896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
14996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
150e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank    public static boolean isPortFieldValid(TextView view) {
151e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        CharSequence chars = view.getText();
152e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        if (TextUtils.isEmpty(chars)) return false;
153e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        Integer port;
154e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        // In theory, we can't get an illegal value here, since the field is monitored for valid
155e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        // numeric input. But this might be used elsewhere without such a check.
156e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        try {
157e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank            port = Integer.parseInt(chars.toString());
158e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        } catch (NumberFormatException e) {
159e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank            return false;
160e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        }
161e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank        return port > 0 && port < 65536;
162ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    }
163ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki
164ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    /**
165ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki     * Validate a hostname name field.
166ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki     *
167ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki     * Because we just use the {@link URI} class for validation, it'll accept some invalid
168ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki     * host names, but it works well enough...
169ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki     */
170ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    public static boolean isServerNameValid(TextView view) {
171ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        return isServerNameValid(view.getText().toString());
172ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    }
173ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki
174ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    public static boolean isServerNameValid(String serverName) {
175ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        serverName = serverName.trim();
176ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        if (TextUtils.isEmpty(serverName)) {
177ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki            return false;
178ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        }
179ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        try {
180ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki            URI uri = new URI(
181ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    "http",
182ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    null,
183ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    serverName,
184ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    -1,
185ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    null, // path
186ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    null, // query
187ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki                    null);
188ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki            return true;
189ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        } catch (URISyntaxException e) {
190ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki            return false;
191ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki        }
192ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki    }
19396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
19496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
19596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * A fast version of  URLDecoder.decode() that works only with UTF-8 and does only two
19696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * allocations. This version is around 3x as fast as the standard one and I'm using it
19796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * hundreds of times in places that slow down the UI, so it helps.
19896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
19996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static String fastUrlDecode(String s) {
20096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
20196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            byte[] bytes = s.getBytes("UTF-8");
20296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            byte ch;
20396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            int length = 0;
20496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            for (int i = 0, count = bytes.length; i < count; i++) {
20596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                ch = bytes[i];
20696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                if (ch == '%') {
20796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    int h = (bytes[i + 1] - '0');
20896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    int l = (bytes[i + 2] - '0');
20996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    if (h > 9) {
21096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                        h -= 7;
21196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    }
21296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    if (l > 9) {
21396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                        l -= 7;
21496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    }
21596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = (byte) ((h << 4) | l);
21696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    i += 2;
21796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
21896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                else if (ch == '+') {
21996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = ' ';
22096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
22196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                else {
22296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    bytes[length] = bytes[i];
22396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
22496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                length++;
22596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
22696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return new String(bytes, 0, length, "UTF-8");
22796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
22896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        catch (UnsupportedEncodingException uee) {
22996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
23096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
23196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
232da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    private final static String HOSTAUTH_WHERE_CREDENTIALS = HostAuthColumns.ADDRESS + " like ?"
2330ff0e155c51250bedc35b5b585003c8cb87f2244Marc Blank            + " and " + HostAuthColumns.LOGIN + " like ?  ESCAPE '\\'"
234da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            + " and " + HostAuthColumns.PROTOCOL + " not like \"smtp\"";
235da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    private final static String ACCOUNT_WHERE_HOSTAUTH = AccountColumns.HOST_AUTH_KEY_RECV + "=?";
236da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler
237da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    /**
238da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * Look for an existing account with the same username & server
239da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     *
240da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * @param context a system context
241da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     * @param allowAccountId this account Id will not trigger (when editing an existing account)
2429d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @param hostName the server's address
2439d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @param userLogin the user's login string
2449d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank     * @result null = no matching account found.  Account = matching account
245da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler     */
2469d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank    public static Account findExistingAccount(Context context, long allowAccountId,
2479d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank            String hostName, String userLogin) {
248da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        ContentResolver resolver = context.getContentResolver();
2490ff0e155c51250bedc35b5b585003c8cb87f2244Marc Blank        String userName = userLogin.replace("_", "\\_");
250da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        Cursor c = resolver.query(HostAuth.CONTENT_URI, HostAuth.ID_PROJECTION,
2510ff0e155c51250bedc35b5b585003c8cb87f2244Marc Blank                HOSTAUTH_WHERE_CREDENTIALS, new String[] { hostName, userName }, null);
25274143e89d538ce74e8c95e04cee56dca9412a7bdMarc Blank        if (c == null) throw new ProviderUnavailableException();
253da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        try {
254da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            while (c.moveToNext()) {
255da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                long hostAuthId = c.getLong(HostAuth.ID_PROJECTION_COLUMN);
2569d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank                // Find account with matching hostauthrecv key, and return it
257da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                Cursor c2 = resolver.query(Account.CONTENT_URI, Account.ID_PROJECTION,
258da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        ACCOUNT_WHERE_HOSTAUTH, new String[] { Long.toString(hostAuthId) }, null);
259da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                try {
260da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    while (c2.moveToNext()) {
261da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        long accountId = c2.getLong(Account.ID_PROJECTION_COLUMN);
262da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        if (accountId != allowAccountId) {
263da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            Account account = Account.restoreAccountWithId(context, accountId);
264da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            if (account != null) {
2659d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank                                return account;
266da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                            }
267da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                        }
268da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    }
269da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                } finally {
270da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                    c2.close();
271da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler                }
272da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            }
273da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        } finally {
274da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler            c.close();
275da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        }
276da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler
277da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler        return null;
278da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler    }
279eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler
280eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    /**
281463584d23f747cfbbd9856b39390269342363b41Tony Mantler     * This only actually matches against the email address. It's technically kosher to allow the
282463584d23f747cfbbd9856b39390269342363b41Tony Mantler     * same address across different account types, but that's a pretty rare use case and isn't well
283463584d23f747cfbbd9856b39390269342363b41Tony Mantler     * handled in the UI.
284463584d23f747cfbbd9856b39390269342363b41Tony Mantler     *
285463584d23f747cfbbd9856b39390269342363b41Tony Mantler     * @param context context
286463584d23f747cfbbd9856b39390269342363b41Tony Mantler     * @param syncAuthority the account manager type to check against or null for all types
287463584d23f747cfbbd9856b39390269342363b41Tony Mantler     * @param address email address to match against
288463584d23f747cfbbd9856b39390269342363b41Tony Mantler     * @return account name for match found or null
289463584d23f747cfbbd9856b39390269342363b41Tony Mantler     */
290463584d23f747cfbbd9856b39390269342363b41Tony Mantler   public static String findExistingAccount(final Context context, final String syncAuthority,
291463584d23f747cfbbd9856b39390269342363b41Tony Mantler           final String address) {
292463584d23f747cfbbd9856b39390269342363b41Tony Mantler       final ContentResolver resolver = context.getContentResolver();
293463584d23f747cfbbd9856b39390269342363b41Tony Mantler       final Cursor c = resolver.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION,
294463584d23f747cfbbd9856b39390269342363b41Tony Mantler               AccountColumns.EMAIL_ADDRESS + "=?", new String[] {address}, null);
295463584d23f747cfbbd9856b39390269342363b41Tony Mantler       try {
296463584d23f747cfbbd9856b39390269342363b41Tony Mantler           if (!c.moveToFirst()) {
297463584d23f747cfbbd9856b39390269342363b41Tony Mantler               return null;
298463584d23f747cfbbd9856b39390269342363b41Tony Mantler           }
299463584d23f747cfbbd9856b39390269342363b41Tony Mantler           return c.getString(c.getColumnIndex(Account.DISPLAY_NAME));
300463584d23f747cfbbd9856b39390269342363b41Tony Mantler           /*
301463584d23f747cfbbd9856b39390269342363b41Tony Mantler           do {
302463584d23f747cfbbd9856b39390269342363b41Tony Mantler               if (syncAuthority != null) {
303463584d23f747cfbbd9856b39390269342363b41Tony Mantler                   // TODO: actually compare the sync authority to allow creating the same account
304463584d23f747cfbbd9856b39390269342363b41Tony Mantler                   // on different protocols. Sadly this code can't directly access the service info
305463584d23f747cfbbd9856b39390269342363b41Tony Mantler               } else {
306463584d23f747cfbbd9856b39390269342363b41Tony Mantler                   final Account account = new Account();
307463584d23f747cfbbd9856b39390269342363b41Tony Mantler                   account.restore(c);
308463584d23f747cfbbd9856b39390269342363b41Tony Mantler                   return account.mDisplayName;
309463584d23f747cfbbd9856b39390269342363b41Tony Mantler               }
310463584d23f747cfbbd9856b39390269342363b41Tony Mantler           } while (c.moveToNext());
311463584d23f747cfbbd9856b39390269342363b41Tony Mantler           */
312463584d23f747cfbbd9856b39390269342363b41Tony Mantler       } finally {
313463584d23f747cfbbd9856b39390269342363b41Tony Mantler           c.close();
314463584d23f747cfbbd9856b39390269342363b41Tony Mantler       }
315463584d23f747cfbbd9856b39390269342363b41Tony Mantler       /*
316463584d23f747cfbbd9856b39390269342363b41Tony Mantler       return null;
317463584d23f747cfbbd9856b39390269342363b41Tony Mantler       */
318463584d23f747cfbbd9856b39390269342363b41Tony Mantler   }
319463584d23f747cfbbd9856b39390269342363b41Tony Mantler
320463584d23f747cfbbd9856b39390269342363b41Tony Mantler    /**
321eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler     * Generate a random message-id header for locally-generated messages.
322eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler     */
323eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    public static String generateMessageId() {
324eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        StringBuffer sb = new StringBuffer();
325eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append("<");
326eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        for (int i = 0; i < 24; i++) {
327eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler            sb.append(Integer.toString((int)(Math.random() * 35), 36));
328eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        }
329eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append(".");
330eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append(Long.toString(System.currentTimeMillis()));
331eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        sb.append("@email.android.com>");
332eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler        return sb.toString();
333eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler    }
334eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler
335989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
336989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a time in milliseconds from a date string that represents a date/time in GMT
337fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * @param date string in format 20090211T180303Z (rfc2445, iCalendar).
338989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the time in milliseconds (since Jan 1, 1970)
339989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
340989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static long parseDateTimeToMillis(String date) {
341989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = parseDateTimeToCalendar(date);
342989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal.getTimeInMillis();
343989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
344989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda
345989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
346989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a GregorianCalendar from a date string that represents a date/time in GMT
347fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * @param date string in format 20090211T180303Z (rfc2445, iCalendar).
348989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the GregorianCalendar
349989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
350989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static GregorianCalendar parseDateTimeToCalendar(String date) {
351989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
352989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(4, 6)) - 1, Integer.parseInt(date.substring(6, 8)),
353989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(9, 11)), Integer.parseInt(date.substring(11, 13)),
354989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(13, 15)));
355989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
356989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal;
357989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
358989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda
359989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    /**
360989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * Generate a time in milliseconds from an email date string that represents a date/time in GMT
361fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * @param date string in format 2010-02-23T16:00:00.000Z (ISO 8601, rfc3339)
362989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     * @return the time in milliseconds (since Jan 1, 1970)
363989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda     */
364989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    public static long parseEmailDateTimeToMillis(String date) {
365989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
366989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(5, 7)) - 1, Integer.parseInt(date.substring(8, 10)),
367989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(11, 13)), Integer.parseInt(date.substring(14, 16)),
368989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Integer.parseInt(date.substring(17, 19)));
369989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
370989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        return cal.getTimeInMillis();
371989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda    }
37220225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki
3737e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private static byte[] encode(Charset charset, String s) {
37420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        if (s == null) {
37520225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki            return null;
37620225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        }
3777e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        final ByteBuffer buffer = charset.encode(CharBuffer.wrap(s));
37820225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        final byte[] bytes = new byte[buffer.limit()];
37920225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        buffer.get(bytes);
38020225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        return bytes;
38120225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki    }
38288a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki
3837e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private static String decode(Charset charset, byte[] b) {
384fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        if (b == null) {
385fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki            return null;
386fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        }
3877e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        final CharBuffer cb = charset.decode(ByteBuffer.wrap(b));
388fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        return new String(cb.array(), 0, cb.length());
389fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki    }
390fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki
3917e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Converts a String to UTF-8 */
3927e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static byte[] toUtf8(String s) {
3937e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return encode(UTF_8, s);
3947e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
3957e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
3967e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Builds a String from UTF-8 bytes */
3977e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static String fromUtf8(byte[] b) {
3987e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return decode(UTF_8, b);
3997e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
4007e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
4017e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Converts a String to ASCII bytes */
4027e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static byte[] toAscii(String s) {
4037e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return encode(ASCII, s);
4047e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
4057e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
4067e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /** Builds a String from ASCII bytes */
4077e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static String fromAscii(byte[] b) {
4087e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return decode(ASCII, b);
4097e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
4107e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
41188a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    /**
41288a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki     * @return true if the input is the first (or only) byte in a UTF-8 character
41388a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki     */
41488a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    public static boolean isFirstUtf8Byte(byte b) {
41588a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki        // If the top 2 bits is '10', it's not a first byte.
41688a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki        return (b & 0xc0) != 0x80;
41788a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki    }
418dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki
419dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    public static String byteToHex(int b) {
420dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        return byteToHex(new StringBuilder(), b).toString();
421dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    }
422dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki
423dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    public static StringBuilder byteToHex(StringBuilder sb, int b) {
424dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        b &= 0xFF;
425dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        sb.append("0123456789ABCDEF".charAt(b >> 4));
426dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        sb.append("0123456789ABCDEF".charAt(b & 0xF));
427dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki        return sb;
428dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki    }
429eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank
430eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank    public static String replaceBareLfWithCrlf(String str) {
431eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank        return str.replace("\r", "").replace("\n", "\r\n");
432eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank    }
43359cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki
43459cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    /**
43559cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * Cancel an {@link AsyncTask}.  If it's already running, it'll be interrupted.
43659cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     */
43759cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    public static void cancelTaskInterrupt(AsyncTask<?, ?, ?> task) {
43859cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        cancelTask(task, true);
43959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    }
44059cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki
44159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    /**
4424a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki     * Cancel an {@link EmailAsyncTask}.  If it's already running, it'll be interrupted.
4434a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki     */
4444a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki    public static void cancelTaskInterrupt(EmailAsyncTask<?, ?, ?> task) {
4454a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki        if (task != null) {
4464a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki            task.cancel(true);
4474a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki        }
4484a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki    }
4494a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki
4504a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki    /**
45159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * Cancel an {@link AsyncTask}.
45259cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *
45359cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
45459cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *        task should be interrupted; otherwise, in-progress tasks are allowed
45559cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     *        to complete.
45659cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki     */
45759cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) {
45859cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) {
45959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki            task.cancel(mayInterruptIfRunning);
46059cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki        }
46159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki    }
462d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki
4633a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank    public static String getSmallHash(final String value) {
464d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final MessageDigest sha;
465d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        try {
466d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            sha = MessageDigest.getInstance("SHA-1");
467d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        } catch (NoSuchAlgorithmException impossible) {
468d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki            return null;
469d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        }
470697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onuki        sha.update(Utility.toUtf8(value));
471d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final int hash = getSmallHashFromSha1(sha.digest());
472d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        return Integer.toString(hash);
473d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    }
474d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki
475d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    /**
476d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     * @return a non-negative integer generated from 20 byte SHA-1 hash.
477d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki     */
478d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    /* package for testing */ static int getSmallHashFromSha1(byte[] sha1) {
479d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        final int offset = sha1[19] & 0xf; // SHA1 is 20 bytes.
480d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki        return ((sha1[offset]  & 0x7f) << 24)
481d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 1] & 0xff) << 16)
482d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 2] & 0xff) << 8)
483d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki                | ((sha1[offset + 3] & 0xff));
484d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki    }
485128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki
486128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    /**
487128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * Try to make a date MIME(RFC 2822/5322)-compliant.
488128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *
489128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * It fixes:
490128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     * - "Thu, 10 Dec 09 15:08:08 GMT-0700" to "Thu, 10 Dec 09 15:08:08 -0700"
491128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *   (4 digit zone value can't be preceded by "GMT")
492128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     *   We got a report saying eBay sends a date in this format
493128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki     */
494128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    public static String cleanUpMimeDate(String date) {
495128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        if (TextUtils.isEmpty(date)) {
496128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki            return date;
497128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        }
498128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        date = DATE_CLEANUP_PATTERN_WRONG_TIMEZONE.matcher(date).replaceFirst("$1");
499128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki        return date;
500128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki    }
5017e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
5027e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static ByteArrayInputStream streamFromAsciiString(String ascii) {
5037e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return new ByteArrayInputStream(toAscii(ascii));
5047e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
5057e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki
5067e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki    /**
507f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     * A thread safe way to show a Toast.  Can be called from any thread.
5087e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     *
509f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     * @param context context
5107e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     * @param resId Resource ID of the message string.
5117e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki     */
512f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    public static void showToast(Context context, int resId) {
513f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki        showToast(context, context.getResources().getString(resId));
51491d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    }
51591d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki
51691d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki    /**
517f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     * A thread safe way to show a Toast.  Can be called from any thread.
51891d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     *
519f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki     * @param context context
52091d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     * @param message Message to show.
52191d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki     */
522f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki    public static void showToast(final Context context, final String message) {
523f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki        getMainThreadHandler().post(new Runnable() {
5245701e0a555a5c263862156c1291aa13b06850425Todd Kennedy            @Override
5257e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki            public void run() {
526f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki                Toast.makeText(context, message, Toast.LENGTH_LONG).show();
5277e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki            }
5287e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki        });
5297e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki    }
5303f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki
5313f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    /**
532b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * Run {@code r} on a worker thread, returning the AsyncTask
533b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * @return the AsyncTask; this is primarily for use by unit tests, which require the
534b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * result of the task
535d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki     *
536d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki     * @deprecated use {@link EmailAsyncTask#runAsyncParallel} or
537d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki     *     {@link EmailAsyncTask#runAsyncSerial}
5383f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki     */
539d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki    @Deprecated
540b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank    public static AsyncTask<Void, Void, Void> runAsync(final Runnable r) {
541b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank        return new AsyncTask<Void, Void, Void>() {
5423f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            @Override protected Void doInBackground(Void... params) {
5433f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                r.run();
5443f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                return null;
5453f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            }
546d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
5473f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    }
54844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
54944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /**
55044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * Interface used in {@link #createUniqueFile} instead of {@link File#createNewFile()} to make
55144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * it testable.
55244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     */
55344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /* package */ interface NewFileCreator {
55444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        public static final NewFileCreator DEFAULT = new NewFileCreator() {
55544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                    @Override public boolean createNewFile(File f) throws IOException {
55644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                        return f.createNewFile();
55744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                    }
55844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        };
55944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        public boolean createNewFile(File f) throws IOException ;
56044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
56144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
56244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /**
56344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * Creates a new empty file with a unique name in the given directory by appending a hyphen and
56444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * a number to the given filename.
56544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     *
56644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     * @return a new File object, or null if one could not be created
56744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki     */
56844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    public static File createUniqueFile(File directory, String filename) throws IOException {
56944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        return createUniqueFileInternal(NewFileCreator.DEFAULT, directory, filename);
57044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
57144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
57244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    /* package */ static File createUniqueFileInternal(NewFileCreator nfc,
57344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            File directory, String filename) throws IOException {
57444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        File file = new File(directory, filename);
57544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        if (nfc.createNewFile(file)) {
57644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            return file;
57744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
57844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        // Get the extension of the file, if any.
57944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        int index = filename.lastIndexOf('.');
58044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        String format;
58144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        if (index != -1) {
58244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            String name = filename.substring(0, index);
58344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            String extension = filename.substring(index);
58444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            format = name + "-%d" + extension;
58544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        } else {
58644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            format = filename + "-%d";
58744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
58844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki
58944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        for (int i = 2; i < Integer.MAX_VALUE; i++) {
59044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            file = new File(directory, String.format(format, i));
59144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            if (nfc.createNewFile(file)) {
59244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki                return file;
59344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki            }
59444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        }
59544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki        return null;
59644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki    }
597bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki
59807597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public interface CursorGetter<T> {
59907597e547bc02cd2247caa866d25b94745dcd448Marc Blank        T get(Cursor cursor, int column);
60007597e547bc02cd2247caa866d25b94745dcd448Marc Blank    }
60107597e547bc02cd2247caa866d25b94745dcd448Marc Blank
60207597e547bc02cd2247caa866d25b94745dcd448Marc Blank    private static final CursorGetter<Long> LONG_GETTER = new CursorGetter<Long>() {
6035701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        @Override
60407597e547bc02cd2247caa866d25b94745dcd448Marc Blank        public Long get(Cursor cursor, int column) {
60507597e547bc02cd2247caa866d25b94745dcd448Marc Blank            return cursor.getLong(column);
60607597e547bc02cd2247caa866d25b94745dcd448Marc Blank        }
60707597e547bc02cd2247caa866d25b94745dcd448Marc Blank    };
60807597e547bc02cd2247caa866d25b94745dcd448Marc Blank
60907597e547bc02cd2247caa866d25b94745dcd448Marc Blank    private static final CursorGetter<Integer> INT_GETTER = new CursorGetter<Integer>() {
6105701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        @Override
61107597e547bc02cd2247caa866d25b94745dcd448Marc Blank        public Integer get(Cursor cursor, int column) {
61207597e547bc02cd2247caa866d25b94745dcd448Marc Blank            return cursor.getInt(column);
61307597e547bc02cd2247caa866d25b94745dcd448Marc Blank        }
61407597e547bc02cd2247caa866d25b94745dcd448Marc Blank    };
61507597e547bc02cd2247caa866d25b94745dcd448Marc Blank
616ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    private static final CursorGetter<String> STRING_GETTER = new CursorGetter<String>() {
6175701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        @Override
618ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki        public String get(Cursor cursor, int column) {
619ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki            return cursor.getString(column);
620ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki        }
621ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    };
622ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki
62307597e547bc02cd2247caa866d25b94745dcd448Marc Blank    private static final CursorGetter<byte[]> BLOB_GETTER = new CursorGetter<byte[]>() {
6245701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        @Override
62507597e547bc02cd2247caa866d25b94745dcd448Marc Blank        public byte[] get(Cursor cursor, int column) {
62607597e547bc02cd2247caa866d25b94745dcd448Marc Blank            return cursor.getBlob(column);
62707597e547bc02cd2247caa866d25b94745dcd448Marc Blank        }
62807597e547bc02cd2247caa866d25b94745dcd448Marc Blank    };
62907597e547bc02cd2247caa866d25b94745dcd448Marc Blank
630bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    /**
6317093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     * @return if {@code original} is to the EmailProvider, add "?limit=1".  Otherwise just returns
6327093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     * {@code original}.
6337093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     *
6347093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     * Other providers don't support the limit param.  Also, changing URI passed from other apps
6357093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     * can cause permission errors.
6367093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki     */
6377093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki    /* package */ static Uri buildLimitOneUri(Uri original) {
6387093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki        if ("content".equals(original.getScheme()) &&
6397093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki                EmailContent.AUTHORITY.equals(original.getAuthority())) {
6407093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki            return EmailContent.uriWithLimit(original, 1);
6417093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki        }
6427093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki        return original;
6437093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki    }
6447093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki
6457093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki    /**
64607597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * @return a generic in column {@code column} of the first result row, if the query returns at
647bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     * least 1 row.  Otherwise returns {@code defaultValue}.
648bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     */
64907597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public static <T extends Object> T getFirstRowColumn(Context context, Uri uri,
65007597e547bc02cd2247caa866d25b94745dcd448Marc Blank            String[] projection, String selection, String[] selectionArgs, String sortOrder,
65107597e547bc02cd2247caa866d25b94745dcd448Marc Blank            int column, T defaultValue, CursorGetter<T> getter) {
65207597e547bc02cd2247caa866d25b94745dcd448Marc Blank        // Use PARAMETER_LIMIT to restrict the query to the single row we need
6537093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki        uri = buildLimitOneUri(uri);
654bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        Cursor c = context.getContentResolver().query(uri, projection, selection, selectionArgs,
655bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki                sortOrder);
656b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki        if (c != null) {
657b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            try {
658b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki                if (c.moveToFirst()) {
65907597e547bc02cd2247caa866d25b94745dcd448Marc Blank                    return getter.get(c, column);
660b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki                }
661b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            } finally {
662b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki                c.close();
663bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            }
664bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        }
665bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki        return defaultValue;
666bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    }
667bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki
668bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    /**
66907597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for a Long with null as a default value.
670bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki     */
671bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    public static Long getFirstRowLong(Context context, Uri uri, String[] projection,
672bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column) {
67307597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
67407597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, null, LONG_GETTER);
675bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki    }
676833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki
677833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki    /**
67807597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for a Long with a provided default value.
67936bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     */
68007597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public static Long getFirstRowLong(Context context, Uri uri, String[] projection,
68136bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column,
68207597e547bc02cd2247caa866d25b94745dcd448Marc Blank            Long defaultValue) {
68307597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
68407597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, defaultValue, LONG_GETTER);
68536bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    }
68636bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki
68736bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    /**
68807597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for an Integer with null as a default value.
68936bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki     */
69036bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    public static Integer getFirstRowInt(Context context, Uri uri, String[] projection,
69136bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column) {
69207597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
69307597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, null, INT_GETTER);
69407597e547bc02cd2247caa866d25b94745dcd448Marc Blank    }
69507597e547bc02cd2247caa866d25b94745dcd448Marc Blank
69607597e547bc02cd2247caa866d25b94745dcd448Marc Blank    /**
69707597e547bc02cd2247caa866d25b94745dcd448Marc Blank     * {@link #getFirstRowColumn} for an Integer with a provided default value.
69807597e547bc02cd2247caa866d25b94745dcd448Marc Blank     */
69907597e547bc02cd2247caa866d25b94745dcd448Marc Blank    public static Integer getFirstRowInt(Context context, Uri uri, String[] projection,
70007597e547bc02cd2247caa866d25b94745dcd448Marc Blank            String selection, String[] selectionArgs, String sortOrder, int column,
70107597e547bc02cd2247caa866d25b94745dcd448Marc Blank            Integer defaultValue) {
70207597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
70307597e547bc02cd2247caa866d25b94745dcd448Marc Blank                sortOrder, column, defaultValue, INT_GETTER);
70436bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki    }
70536bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki
70607597e547bc02cd2247caa866d25b94745dcd448Marc Blank    /**
707ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki     * {@link #getFirstRowColumn} for a String with null as a default value.
708ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki     */
709ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    public static String getFirstRowString(Context context, Uri uri, String[] projection,
710ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column) {
7110e00160074abffe03285b355dce8b6ae2a6e3138Makoto Onuki        return getFirstRowString(context, uri, projection, selection, selectionArgs, sortOrder,
7120e00160074abffe03285b355dce8b6ae2a6e3138Makoto Onuki                column, null);
713ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    }
714ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki
715ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    /**
716ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki     * {@link #getFirstRowColumn} for a String with a provided default value.
717ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki     */
718ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    public static String getFirstRowString(Context context, Uri uri, String[] projection,
719ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column,
720ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki            String defaultValue) {
721ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki        return getFirstRowColumn(context, uri, projection, selection, selectionArgs,
722ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki                sortOrder, column, defaultValue, STRING_GETTER);
723ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    }
724ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki
725ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki    /**
726ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki     * {@link #getFirstRowColumn} for a byte array with a provided default value.
72707597e547bc02cd2247caa866d25b94745dcd448Marc Blank     */
728b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki    public static byte[] getFirstRowBlob(Context context, Uri uri, String[] projection,
729b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            String selection, String[] selectionArgs, String sortOrder, int column,
730b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki            byte[] defaultValue) {
73107597e547bc02cd2247caa866d25b94745dcd448Marc Blank        return getFirstRowColumn(context, uri, projection, selection, selectionArgs, sortOrder,
73207597e547bc02cd2247caa866d25b94745dcd448Marc Blank                column, defaultValue, BLOB_GETTER);
733b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki    }
734b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki
7354dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank    public static boolean attachmentExists(Context context, Attachment attachment) {
7364dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        if (attachment == null) {
7374dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            return false;
7384dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        } else if (attachment.mContentBytes != null) {
7394dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            return true;
7406e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank        } else {
7415a3aebbd2dd8cdd4d7c1a76ce3085cd6a314c0d0Paul Westbrook            final String cachedFile = attachment.getCachedFileUri();
7429a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook            // Try the cached file first
7439a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook            if (!TextUtils.isEmpty(cachedFile)) {
7445a3aebbd2dd8cdd4d7c1a76ce3085cd6a314c0d0Paul Westbrook                final Uri cachedFileUri = Uri.parse(cachedFile);
7459a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                try {
7465a3aebbd2dd8cdd4d7c1a76ce3085cd6a314c0d0Paul Westbrook                    final InputStream inStream =
7475a3aebbd2dd8cdd4d7c1a76ce3085cd6a314c0d0Paul Westbrook                            context.getContentResolver().openInputStream(cachedFileUri);
7489a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                    try {
7499a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                        inStream.close();
7509a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                    } catch (IOException e) {
7519a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                        // Nothing to be done if can't close the stream
7529a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                    }
7539a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                    return true;
7549a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                } catch (FileNotFoundException e) {
7559a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                    // We weren't able to open the file, try the content uri below
7565a3aebbd2dd8cdd4d7c1a76ce3085cd6a314c0d0Paul Westbrook                    LogUtils.e(Logging.LOG_TAG, e, "not able to open cached file");
7579a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                }
7589a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook            }
7599a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook            final String contentUri = attachment.getContentUri();
7606e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank            if (TextUtils.isEmpty(contentUri)) {
7616e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank                return false;
7626e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank            }
763d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            try {
7649a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                final Uri fileUri = Uri.parse(contentUri);
765567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                try {
7669a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                    final InputStream inStream =
7679a95253846ccc7a94dd7d4c618ec2d808e2a4000Paul Westbrook                            context.getContentResolver().openInputStream(fileUri);
7686e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank                    try {
7696e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank                        inStream.close();
7706e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank                    } catch (IOException e) {
7716e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank                        // Nothing to be done if can't close the stream
7726e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank                    }
7736e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank                    return true;
7746e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank                } catch (FileNotFoundException e) {
7756e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank                    return false;
776567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                }
7776e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank            } catch (RuntimeException re) {
778560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.w(Logging.LOG_TAG, "attachmentExists RuntimeException=" + re);
779567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki                return false;
780d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki            }
781d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki        }
78209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
78309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
78409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
78509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Check whether the message with a given id has unloaded attachments.  If the message is
78609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * a forwarded message, we look instead at the messages's source for the attachments.  If the
78709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * message or forward source can't be found, we return false
78809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @param context the caller's context
78909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @param messageId the id of the message
79009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * @return whether or not the message has unloaded attachments
79109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
79209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static boolean hasUnloadedAttachments(Context context, long messageId) {
79309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Message msg = Message.restoreMessageWithId(context, messageId);
79409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        if (msg == null) return false;
79509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Attachment[] atts = Attachment.restoreAttachmentsWithMessageId(context, messageId);
79609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        for (Attachment att: atts) {
7974dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank            if (!attachmentExists(context, att)) {
79809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // If the attachment doesn't exist and isn't marked for download, we're in trouble
79909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // since the outbound message will be stuck indefinitely in the Outbox.  Instead,
80009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // we'll just delete the attachment and continue; this is far better than the
80109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                // alternative.  In theory, this situation shouldn't be possible.
80209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                if ((att.mFlags & (Attachment.FLAG_DOWNLOAD_FORWARD |
80309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                        Attachment.FLAG_DOWNLOAD_USER_REQUEST)) == 0) {
804560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                    LogUtils.d(Logging.LOG_TAG, "Unloaded attachment isn't marked for download: " +
80509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                            att.mFileName + ", #" + att.mId);
80647925465841af09b60fff7027132ceacec2933b7Marc Blank                    Account acct = Account.restoreAccountWithId(context, msg.mAccountKey);
80747925465841af09b60fff7027132ceacec2933b7Marc Blank                    if (acct == null) return true;
80847925465841af09b60fff7027132ceacec2933b7Marc Blank                    // If smart forward is set and the message is a forward, we'll act as though
80947925465841af09b60fff7027132ceacec2933b7Marc Blank                    // the attachment has been loaded
81047925465841af09b60fff7027132ceacec2933b7Marc Blank                    // In Email1 this test wasn't necessary, as the UI handled it...
81147925465841af09b60fff7027132ceacec2933b7Marc Blank                    if ((msg.mFlags & Message.FLAG_TYPE_FORWARD) != 0) {
81247925465841af09b60fff7027132ceacec2933b7Marc Blank                        if ((acct.mFlags & Account.FLAGS_SUPPORTS_SMART_FORWARD) != 0) {
81347925465841af09b60fff7027132ceacec2933b7Marc Blank                            continue;
81447925465841af09b60fff7027132ceacec2933b7Marc Blank                        }
81547925465841af09b60fff7027132ceacec2933b7Marc Blank                    }
81609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    Attachment.delete(context, Attachment.CONTENT_URI, att.mId);
8176e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank                } else if (att.getContentUri() != null) {
81809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    // In this case, the attachment file is gone from the cache; let's clear the
81909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    // contentUri; this should be a very unusual case
82009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    ContentValues cv = new ContentValues();
82109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    cv.putNull(AttachmentColumns.CONTENT_URI);
82209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    Attachment.update(context, Attachment.CONTENT_URI, att.mId, cv);
82309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                }
82409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                return true;
82509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            }
82609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        }
82709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return false;
82809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
82909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
83009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
83109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Convenience method wrapping calls to retrieve columns from a single row, via EmailProvider.
83209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * The arguments are exactly the same as to contentResolver.query().  Results are returned in
83309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * an array of Strings corresponding to the columns in the projection.  If the cursor has no
83409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * rows, null is returned.
83509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
83609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static String[] getRowColumns(Context context, Uri contentUri, String[] projection,
83709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            String selection, String[] selectionArgs) {
83809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        String[] values = new String[projection.length];
83909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        ContentResolver cr = context.getContentResolver();
84009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        Cursor c = cr.query(contentUri, projection, selection, selectionArgs, null);
84109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        try {
84209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            if (c.moveToFirst()) {
84309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                for (int i = 0; i < projection.length; i++) {
84409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                    values[i] = c.getString(i);
84509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                }
84609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            } else {
84709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                return null;
84809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            }
84909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        } finally {
85009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            c.close();
85109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        }
85209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return values;
85309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
85409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
85509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    /**
85609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Convenience method for retrieving columns from a particular row in EmailProvider.
85709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * Passed in here are a base uri (e.g. Message.CONTENT_URI), the unique id of a row, and
85809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     * a projection.  This method calls the previous one with the appropriate URI.
85909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank     */
86009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static String[] getRowColumns(Context context, Uri baseUri, long id,
86109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank            String ... projection) {
86209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return getRowColumns(context, ContentUris.withAppendedId(baseUri, id), projection, null,
86309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank                null);
86409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
86509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank
86609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    public static boolean isExternalStorageMounted() {
86709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
86809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank    }
869f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
870f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    /**
871f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki     * Class that supports running any operation for each account.
872f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki     */
873f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    public abstract static class ForEachAccount extends AsyncTask<Void, Void, Long[]> {
874f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        private final Context mContext;
875f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
876f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        public ForEachAccount(Context context) {
877f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            mContext = context;
878f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
879f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
880f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        @Override
881f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected final Long[] doInBackground(Void... params) {
882f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            ArrayList<Long> ids = new ArrayList<Long>();
883f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank            Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
884f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank                    Account.ID_PROJECTION, null, null, null);
885f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            try {
886f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                while (c.moveToNext()) {
887f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank                    ids.add(c.getLong(Account.ID_PROJECTION_COLUMN));
888f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                }
889f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            } finally {
890f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                c.close();
891f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            }
892f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            return ids.toArray(EMPTY_LONGS);
893f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
894f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
895f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        @Override
896f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected final void onPostExecute(Long[] ids) {
897f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            if (ids != null && !isCancelled()) {
898f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                for (long id : ids) {
899f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                    performAction(id);
900f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki                }
901f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            }
902f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            onFinished();
903f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
904f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
905f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        /**
906f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         * This method will be called for each account.
907f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         */
908f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected abstract void performAction(long accountId);
909f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
910f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        /**
911f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         * Called when the iteration is finished.
912f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki         */
913f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected void onFinished() {
914f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
915f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    }
916767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki
917767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki    public static long[] toPrimitiveLongArray(Collection<Long> collection) {
9184e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        // Need to do this manually because we're converting to a primitive long array, not
9194e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        // a Long array.
920767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        final int size = collection.size();
921767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        final long[] ret = new long[size];
922767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        // Collection doesn't have get(i).  (Iterable doesn't have size())
923767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        int i = 0;
924767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        for (Long value : collection) {
925767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki            ret[i++] = value;
926767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        }
927767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki        return ret;
928767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki    }
929082443978595bee3b7907563dc7665f908872e20Makoto Onuki
9304e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki    public static Set<Long> toLongSet(long[] array) {
9314e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        // Need to do this manually because we're converting from a primitive long array, not
9324e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        // a Long array.
9334e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        final int size = array.length;
9344e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        HashSet<Long> ret = new HashSet<Long>(size);
9354e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        for (int i = 0; i < size; i++) {
9364e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki            ret.add(array[i]);
9374e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        }
9384e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki        return ret;
9394e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki    }
9404e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki
941082443978595bee3b7907563dc7665f908872e20Makoto Onuki    /**
942082443978595bee3b7907563dc7665f908872e20Makoto Onuki     * Workaround for the {@link ListView#smoothScrollToPosition} randomly scroll the view bug
943082443978595bee3b7907563dc7665f908872e20Makoto Onuki     * if it's called right after {@link ListView#setAdapter}.
944082443978595bee3b7907563dc7665f908872e20Makoto Onuki     */
945082443978595bee3b7907563dc7665f908872e20Makoto Onuki    public static void listViewSmoothScrollToPosition(final Activity activity,
946082443978595bee3b7907563dc7665f908872e20Makoto Onuki            final ListView listView, final int position) {
947082443978595bee3b7907563dc7665f908872e20Makoto Onuki        // Workarond: delay-call smoothScrollToPosition()
948082443978595bee3b7907563dc7665f908872e20Makoto Onuki        new Handler().post(new Runnable() {
949082443978595bee3b7907563dc7665f908872e20Makoto Onuki            @Override
950082443978595bee3b7907563dc7665f908872e20Makoto Onuki            public void run() {
951082443978595bee3b7907563dc7665f908872e20Makoto Onuki                if (activity.isFinishing()) {
952082443978595bee3b7907563dc7665f908872e20Makoto Onuki                    return; // Activity being destroyed
953082443978595bee3b7907563dc7665f908872e20Makoto Onuki                }
954082443978595bee3b7907563dc7665f908872e20Makoto Onuki                listView.smoothScrollToPosition(position);
955082443978595bee3b7907563dc7665f908872e20Makoto Onuki            }
956082443978595bee3b7907563dc7665f908872e20Makoto Onuki        });
957082443978595bee3b7907563dc7665f908872e20Makoto Onuki    }
958d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki
959d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    private static final String[] ATTACHMENT_META_NAME_PROJECTION = {
960d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki        OpenableColumns.DISPLAY_NAME
961d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    };
962d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    private static final int ATTACHMENT_META_NAME_COLUMN_DISPLAY_NAME = 0;
963d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki
964d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    /**
965d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki     * @return Filename of a content of {@code contentUri}.  If the provider doesn't provide the
966d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki     * filename, returns the last path segment of the URI.
967d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki     */
968d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    public static String getContentFileName(Context context, Uri contentUri) {
969d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki        String name = getFirstRowString(context, contentUri, ATTACHMENT_META_NAME_PROJECTION, null,
970d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki                null, null, ATTACHMENT_META_NAME_COLUMN_DISPLAY_NAME);
971d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki        if (name == null) {
972d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki            name = contentUri.getLastPathSegment();
973d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki        }
974d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki        return name;
975d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki    }
976adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
977adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    /**
978a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki     * Append a bold span to a {@link SpannableStringBuilder}.
979a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki     */
980a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki    public static SpannableStringBuilder appendBold(SpannableStringBuilder ssb, String text) {
981a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki        if (!TextUtils.isEmpty(text)) {
982a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki            SpannableString ss = new SpannableString(text);
983a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki            ss.setSpan(new StyleSpan(Typeface.BOLD), 0, ss.length(),
984a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
985a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki            ssb.append(ss);
986a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki        }
987a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki
988a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki        return ssb;
989a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki    }
990a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki
991a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki    /**
992adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     * Stringify a cursor for logging purpose.
993adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     */
994adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    public static String dumpCursor(Cursor c) {
995adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        StringBuilder sb = new StringBuilder();
996adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        sb.append("[");
997adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        while (c != null) {
998adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            sb.append(c.getClass()); // Class name may not be available if toString() is overridden
999adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            sb.append("/");
1000adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            sb.append(c.toString());
1001adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            if (c.isClosed()) {
1002adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                sb.append(" (closed)");
1003adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            }
1004adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            if (c instanceof CursorWrapper) {
1005adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                c = ((CursorWrapper) c).getWrappedCursor();
1006adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                sb.append(", ");
1007adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            } else {
1008adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                break;
1009adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            }
1010adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1011adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        sb.append("]");
1012adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        return sb.toString();
1013adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    }
1014adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1015adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    /**
1016adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     * Cursor wrapper that remembers where it was closed.
1017adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     *
1018adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     * Use {@link #get} to create a wrapped cursor.
1019adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     * USe {@link #getTraceIfAvailable} to get the stack trace.
1020adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     * Use {@link #log} to log if/where it was closed.
1021adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki     */
1022adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    public static class CloseTraceCursorWrapper extends CursorWrapper {
102380a2e9109daa5d13cd748305b4c5f83578f33728Makoto Onuki        private static final boolean TRACE_ENABLED = false;
1024adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1025adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        private Exception mTrace;
1026adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1027adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        private CloseTraceCursorWrapper(Cursor cursor) {
1028adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            super(cursor);
1029adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1030adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1031adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        @Override
1032adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        public void close() {
1033adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            mTrace = new Exception("STACK TRACE");
1034adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            super.close();
1035adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1036adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1037adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        public static Exception getTraceIfAvailable(Cursor c) {
1038adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            if (c instanceof CloseTraceCursorWrapper) {
1039adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                return ((CloseTraceCursorWrapper) c).mTrace;
1040adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            } else {
1041adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                return null;
1042adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            }
1043adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1044adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1045adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        public static void log(Cursor c) {
1046adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            if (c == null) {
1047adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki                return;
1048adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            }
1049adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            if (c.isClosed()) {
1050560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.w(Logging.LOG_TAG, "Cursor was closed here: Cursor=" + c,
105131d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                        getTraceIfAvailable(c));
1052adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            } else {
1053560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.w(Logging.LOG_TAG, "Cursor not closed.  Cursor=" + c);
1054adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            }
1055adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1056adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1057adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        public static Cursor get(Cursor original) {
1058adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            return TRACE_ENABLED ? new CloseTraceCursorWrapper(original) : original;
1059adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1060adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki
1061adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        /* package */ static CloseTraceCursorWrapper alwaysCreateForTest(Cursor original) {
1062adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki            return new CloseTraceCursorWrapper(original);
1063adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki        }
1064adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki    }
10650c75f83f03b3ef8e54163cba2d67ba086cf8af58Makoto Onuki
10660c75f83f03b3ef8e54163cba2d67ba086cf8af58Makoto Onuki    /**
1067fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     * Test that the given strings are equal in a null-pointer safe fashion.
1068fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy     */
1069fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy    public static boolean areStringsEqual(String s1, String s2) {
1070fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy        return (s1 != null && s1.equals(s2)) || (s1 == null && s2 == null);
1071fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy    }
107219b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki
107319b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki    public static void enableStrictMode(boolean enabled) {
107419b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki        StrictMode.setThreadPolicy(enabled
107519b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki                ? new StrictMode.ThreadPolicy.Builder().detectAll().build()
107619b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki                : StrictMode.ThreadPolicy.LAX);
107719b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki        StrictMode.setVmPolicy(enabled
107819b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki                ? new StrictMode.VmPolicy.Builder().detectAll().build()
107919b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki                : StrictMode.VmPolicy.LAX);
108019b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki    }
1081d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki
1082d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki    public static String dumpFragment(Fragment f) {
1083d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki        StringWriter sw = new StringWriter();
1084d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki        PrintWriter w = new PrintWriter(sw);
1085d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki        f.dump("", new FileDescriptor(), w, new String[0]);
1086d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki        return sw.toString();
1087d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki    }
10888de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki
10898de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki    /**
10908de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki     * Builds an "in" expression for SQLite.
10918de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki     *
10928de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki     * e.g. "ID" + 1,2,3 -> "ID in (1,2,3)".  If {@code values} is empty or null, it returns an
10938de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki     * empty string.
10948de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki     */
10958de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki    public static String buildInSelection(String columnName, Collection<? extends Number> values) {
10968de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        if ((values == null) || (values.size() == 0)) {
10978de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki            return "";
10988de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        }
10998de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        StringBuilder sb = new StringBuilder();
11008de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        sb.append(columnName);
11018de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        sb.append(" in (");
11028de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        String sep = "";
11038de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        for (Number n : values) {
11048de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki            sb.append(sep);
11058de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki            sb.append(n.toString());
11068de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki            sep = ",";
11078de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        }
11088de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        sb.append(')');
11098de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki        return sb.toString();
11108de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki    }
111196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
1112