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