15c523858385176c33a7456bb84035de78552d22dMarc Blank/*
25c523858385176c33a7456bb84035de78552d22dMarc Blank * Copyright (C) 2008 The Android Open Source Project
35c523858385176c33a7456bb84035de78552d22dMarc Blank *
45c523858385176c33a7456bb84035de78552d22dMarc Blank * Licensed under the Apache License, Version 2.0 (the "License");
55c523858385176c33a7456bb84035de78552d22dMarc Blank * you may not use this file except in compliance with the License.
65c523858385176c33a7456bb84035de78552d22dMarc Blank * You may obtain a copy of the License at
75c523858385176c33a7456bb84035de78552d22dMarc Blank *
85c523858385176c33a7456bb84035de78552d22dMarc Blank *      http://www.apache.org/licenses/LICENSE-2.0
95c523858385176c33a7456bb84035de78552d22dMarc Blank *
105c523858385176c33a7456bb84035de78552d22dMarc Blank * Unless required by applicable law or agreed to in writing, software
115c523858385176c33a7456bb84035de78552d22dMarc Blank * distributed under the License is distributed on an "AS IS" BASIS,
125c523858385176c33a7456bb84035de78552d22dMarc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135c523858385176c33a7456bb84035de78552d22dMarc Blank * See the License for the specific language governing permissions and
145c523858385176c33a7456bb84035de78552d22dMarc Blank * limitations under the License.
155c523858385176c33a7456bb84035de78552d22dMarc Blank */
165c523858385176c33a7456bb84035de78552d22dMarc Blank
175c523858385176c33a7456bb84035de78552d22dMarc Blankpackage com.android.email.mail.store;
185c523858385176c33a7456bb84035de78552d22dMarc Blank
195c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.content.Context;
205c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.os.Build;
215c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.os.Bundle;
225c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.telephony.TelephonyManager;
235c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.text.TextUtils;
245c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.util.Base64;
255c523858385176c33a7456bb84035de78552d22dMarc Blank
265c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.LegacyConversions;
275c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.Preferences;
285c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.mail.Store;
295c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.mail.store.imap.ImapConstants;
305c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.mail.store.imap.ImapResponse;
315c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.mail.store.imap.ImapString;
325c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.mail.transport.MailTransport;
335c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.Logging;
345c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.VendorPolicyLoader;
355c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.internet.MimeMessage;
365c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.AuthenticationFailedException;
375c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Flag;
385c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Folder;
395c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Message;
405c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.MessagingException;
415c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.provider.Account;
42e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.emailcommon.provider.Credential;
4379cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdonimport com.android.emailcommon.provider.EmailContent;
445c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.provider.HostAuth;
455c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.provider.Mailbox;
465c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.service.EmailServiceProxy;
475c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.utility.Utility;
48560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils;
495c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.beetstra.jutf7.CharsetProvider;
505c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.google.common.annotations.VisibleForTesting;
515c523858385176c33a7456bb84035de78552d22dMarc Blank
525c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.io.IOException;
535c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.io.InputStream;
545c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.nio.ByteBuffer;
555c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.nio.charset.Charset;
565c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.security.MessageDigest;
575c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.security.NoSuchAlgorithmException;
585c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.Collection;
595c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.HashMap;
605c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.List;
615c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.Set;
625c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.concurrent.ConcurrentLinkedQueue;
635c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.regex.Pattern;
645c523858385176c33a7456bb84035de78552d22dMarc Blank
655c523858385176c33a7456bb84035de78552d22dMarc Blank
665c523858385176c33a7456bb84035de78552d22dMarc Blank/**
675c523858385176c33a7456bb84035de78552d22dMarc Blank * <pre>
685c523858385176c33a7456bb84035de78552d22dMarc Blank * TODO Need to start keeping track of UIDVALIDITY
695c523858385176c33a7456bb84035de78552d22dMarc Blank * TODO Need a default response handler for things like folder updates
705c523858385176c33a7456bb84035de78552d22dMarc Blank * TODO In fetch(), if we need a ImapMessage and were given
715c523858385176c33a7456bb84035de78552d22dMarc Blank *      something else we can try to do a pre-fetch first.
725c523858385176c33a7456bb84035de78552d22dMarc Blank * TODO Collect ALERT messages and show them to users.
735c523858385176c33a7456bb84035de78552d22dMarc Blank *
745c523858385176c33a7456bb84035de78552d22dMarc Blank * ftp://ftp.isi.edu/in-notes/rfc2683.txt When a client asks for
755c523858385176c33a7456bb84035de78552d22dMarc Blank * certain information in a FETCH command, the server may return the requested
765c523858385176c33a7456bb84035de78552d22dMarc Blank * information in any order, not necessarily in the order that it was requested.
775c523858385176c33a7456bb84035de78552d22dMarc Blank * Further, the server may return the information in separate FETCH responses
785c523858385176c33a7456bb84035de78552d22dMarc Blank * and may also return information that was not explicitly requested (to reflect
795c523858385176c33a7456bb84035de78552d22dMarc Blank * to the client changes in the state of the subject message).
805c523858385176c33a7456bb84035de78552d22dMarc Blank * </pre>
815c523858385176c33a7456bb84035de78552d22dMarc Blank */
825c523858385176c33a7456bb84035de78552d22dMarc Blankpublic class ImapStore extends Store {
835c523858385176c33a7456bb84035de78552d22dMarc Blank    /** Charset used for converting folder names to and from UTF-7 as defined by RFC 3501. */
845c523858385176c33a7456bb84035de78552d22dMarc Blank    private static final Charset MODIFIED_UTF_7_CHARSET =
855c523858385176c33a7456bb84035de78552d22dMarc Blank            new CharsetProvider().charsetForName("X-RFC-3501");
865c523858385176c33a7456bb84035de78552d22dMarc Blank
875c523858385176c33a7456bb84035de78552d22dMarc Blank    @VisibleForTesting static String sImapId = null;
885c523858385176c33a7456bb84035de78552d22dMarc Blank    @VisibleForTesting String mPathPrefix;
895c523858385176c33a7456bb84035de78552d22dMarc Blank    @VisibleForTesting String mPathSeparator;
905c523858385176c33a7456bb84035de78552d22dMarc Blank
91e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    private boolean mUseOAuth;
92e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon
935c523858385176c33a7456bb84035de78552d22dMarc Blank    private final ConcurrentLinkedQueue<ImapConnection> mConnectionPool =
945c523858385176c33a7456bb84035de78552d22dMarc Blank            new ConcurrentLinkedQueue<ImapConnection>();
955c523858385176c33a7456bb84035de78552d22dMarc Blank
965c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
975c523858385176c33a7456bb84035de78552d22dMarc Blank     * Static named constructor.
985c523858385176c33a7456bb84035de78552d22dMarc Blank     */
995c523858385176c33a7456bb84035de78552d22dMarc Blank    public static Store newInstance(Account account, Context context) throws MessagingException {
1005c523858385176c33a7456bb84035de78552d22dMarc Blank        return new ImapStore(context, account);
1015c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1025c523858385176c33a7456bb84035de78552d22dMarc Blank
1035c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1045c523858385176c33a7456bb84035de78552d22dMarc Blank     * Creates a new store for the given account. Always use
1055c523858385176c33a7456bb84035de78552d22dMarc Blank     * {@link #newInstance(Account, Context)} to create an IMAP store.
1065c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1075c523858385176c33a7456bb84035de78552d22dMarc Blank    private ImapStore(Context context, Account account) throws MessagingException {
1085c523858385176c33a7456bb84035de78552d22dMarc Blank        mContext = context;
1095c523858385176c33a7456bb84035de78552d22dMarc Blank        mAccount = account;
1105c523858385176c33a7456bb84035de78552d22dMarc Blank
1115c523858385176c33a7456bb84035de78552d22dMarc Blank        HostAuth recvAuth = account.getOrCreateHostAuthRecv(context);
1125c523858385176c33a7456bb84035de78552d22dMarc Blank        if (recvAuth == null) {
1135c523858385176c33a7456bb84035de78552d22dMarc Blank            throw new MessagingException("No HostAuth in ImapStore?");
1145c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1155c523858385176c33a7456bb84035de78552d22dMarc Blank        mTransport = new MailTransport(context, "IMAP", recvAuth);
1165c523858385176c33a7456bb84035de78552d22dMarc Blank
1175c523858385176c33a7456bb84035de78552d22dMarc Blank        String[] userInfo = recvAuth.getLogin();
11876472ae40cd55d17edb0420e8fc2a7bae60c50deTony Mantler        mUsername = userInfo[0];
11976472ae40cd55d17edb0420e8fc2a7bae60c50deTony Mantler        mPassword = userInfo[1];
120e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        final Credential cred = recvAuth.getCredential(context);
121e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        mUseOAuth = (cred != null);
1225c523858385176c33a7456bb84035de78552d22dMarc Blank        mPathPrefix = recvAuth.mDomain;
1235c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1245c523858385176c33a7456bb84035de78552d22dMarc Blank
125e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    boolean getUseOAuth() {
126e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        return mUseOAuth;
127e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    }
128e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon
129e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    String getUsername() {
130e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        return mUsername;
131e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    }
132e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon
133e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    String getPassword() {
134e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        return mPassword;
135e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    }
136e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon
13779cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon    public boolean canSyncFolderType(final int type) {
13879cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon        switch (type) {
13979cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_INBOX:
14079cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_MAIL:
14179cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_SENT:
14279cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_TRASH:
14379cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_JUNK:
14479cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon                return true;
14579cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_NONE:
14679cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_PARENT:
14779cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_DRAFTS:
14879cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_OUTBOX:
14979cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_SEARCH:
15079cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_STARRED:
15179cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            case Mailbox.TYPE_UNREAD:
15279cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            default:
15379cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon                return false;
15479cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon        }
15579cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon    }
15679cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon
1575c523858385176c33a7456bb84035de78552d22dMarc Blank    @VisibleForTesting
1585c523858385176c33a7456bb84035de78552d22dMarc Blank    Collection<ImapConnection> getConnectionPoolForTest() {
1595c523858385176c33a7456bb84035de78552d22dMarc Blank        return mConnectionPool;
1605c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1615c523858385176c33a7456bb84035de78552d22dMarc Blank
1625c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1635c523858385176c33a7456bb84035de78552d22dMarc Blank     * For testing only.  Injects a different root transport (it will be copied using
1645c523858385176c33a7456bb84035de78552d22dMarc Blank     * newInstanceWithConfiguration() each time IMAP sets up a new channel).  The transport
1655c523858385176c33a7456bb84035de78552d22dMarc Blank     * should already be set up and ready to use.  Do not use for real code.
1665c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param testTransport The Transport to inject and use for all future communication.
1675c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1685c523858385176c33a7456bb84035de78552d22dMarc Blank    @VisibleForTesting
1695c523858385176c33a7456bb84035de78552d22dMarc Blank    void setTransportForTest(MailTransport testTransport) {
1705c523858385176c33a7456bb84035de78552d22dMarc Blank        mTransport = testTransport;
1715c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1725c523858385176c33a7456bb84035de78552d22dMarc Blank
1735c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1745c523858385176c33a7456bb84035de78552d22dMarc Blank     * Return, or create and return, an string suitable for use in an IMAP ID message.
1755c523858385176c33a7456bb84035de78552d22dMarc Blank     * This is constructed similarly to the way the browser sets up its user-agent strings.
1765c523858385176c33a7456bb84035de78552d22dMarc Blank     * See RFC 2971 for more details.  The output of this command will be a series of key-value
1775c523858385176c33a7456bb84035de78552d22dMarc Blank     * pairs delimited by spaces (there is no point in returning a structured result because
1785c523858385176c33a7456bb84035de78552d22dMarc Blank     * this will be sent as-is to the IMAP server).  No tokens, parenthesis or "ID" are included,
1795c523858385176c33a7456bb84035de78552d22dMarc Blank     * because some connections may append additional values.
1805c523858385176c33a7456bb84035de78552d22dMarc Blank     *
1815c523858385176c33a7456bb84035de78552d22dMarc Blank     * The following IMAP ID keys may be included:
1825c523858385176c33a7456bb84035de78552d22dMarc Blank     *   name                   Android package name of the program
1835c523858385176c33a7456bb84035de78552d22dMarc Blank     *   os                     "android"
1845c523858385176c33a7456bb84035de78552d22dMarc Blank     *   os-version             "version; model; build-id"
1855c523858385176c33a7456bb84035de78552d22dMarc Blank     *   vendor                 Vendor of the client/server
1865c523858385176c33a7456bb84035de78552d22dMarc Blank     *   x-android-device-model Model (only revealed if release build)
1875c523858385176c33a7456bb84035de78552d22dMarc Blank     *   x-android-net-operator Mobile network operator (if known)
1885c523858385176c33a7456bb84035de78552d22dMarc Blank     *   AGUID                  A device+account UID
1895c523858385176c33a7456bb84035de78552d22dMarc Blank     *
1905c523858385176c33a7456bb84035de78552d22dMarc Blank     * In addition, a vendor policy .apk can append key/value pairs.
1915c523858385176c33a7456bb84035de78552d22dMarc Blank     *
1925c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param userName the username of the account
1935c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param host the host (server) of the account
1945c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param capabilities a list of the capabilities from the server
1955c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return a String for use in an IMAP ID message.
1965c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1975c523858385176c33a7456bb84035de78552d22dMarc Blank    public static String getImapId(Context context, String userName, String host,
1985c523858385176c33a7456bb84035de78552d22dMarc Blank            String capabilities) {
1995c523858385176c33a7456bb84035de78552d22dMarc Blank        // The first section is global to all IMAP connections, and generates the fixed
2005c523858385176c33a7456bb84035de78552d22dMarc Blank        // values in any IMAP ID message
2015c523858385176c33a7456bb84035de78552d22dMarc Blank        synchronized (ImapStore.class) {
2025c523858385176c33a7456bb84035de78552d22dMarc Blank            if (sImapId == null) {
2035c523858385176c33a7456bb84035de78552d22dMarc Blank                TelephonyManager tm =
2045c523858385176c33a7456bb84035de78552d22dMarc Blank                        (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
2055c523858385176c33a7456bb84035de78552d22dMarc Blank                String networkOperator = tm.getNetworkOperatorName();
2065c523858385176c33a7456bb84035de78552d22dMarc Blank                if (networkOperator == null) networkOperator = "";
2075c523858385176c33a7456bb84035de78552d22dMarc Blank
2085c523858385176c33a7456bb84035de78552d22dMarc Blank                sImapId = makeCommonImapId(context.getPackageName(), Build.VERSION.RELEASE,
2095c523858385176c33a7456bb84035de78552d22dMarc Blank                        Build.VERSION.CODENAME, Build.MODEL, Build.ID, Build.MANUFACTURER,
2105c523858385176c33a7456bb84035de78552d22dMarc Blank                        networkOperator);
2115c523858385176c33a7456bb84035de78552d22dMarc Blank            }
2125c523858385176c33a7456bb84035de78552d22dMarc Blank        }
2135c523858385176c33a7456bb84035de78552d22dMarc Blank
2145c523858385176c33a7456bb84035de78552d22dMarc Blank        // This section is per Store, and adds in a dynamic elements like UID's.
2155c523858385176c33a7456bb84035de78552d22dMarc Blank        // We don't cache the result of this work, because the caller does anyway.
2165c523858385176c33a7456bb84035de78552d22dMarc Blank        StringBuilder id = new StringBuilder(sImapId);
2175c523858385176c33a7456bb84035de78552d22dMarc Blank
2185c523858385176c33a7456bb84035de78552d22dMarc Blank        // Optionally add any vendor-supplied id keys
2195c523858385176c33a7456bb84035de78552d22dMarc Blank        String vendorId = VendorPolicyLoader.getInstance(context).getImapIdValues(userName, host,
2205c523858385176c33a7456bb84035de78552d22dMarc Blank                capabilities);
2215c523858385176c33a7456bb84035de78552d22dMarc Blank        if (vendorId != null) {
2225c523858385176c33a7456bb84035de78552d22dMarc Blank            id.append(' ');
2235c523858385176c33a7456bb84035de78552d22dMarc Blank            id.append(vendorId);
2245c523858385176c33a7456bb84035de78552d22dMarc Blank        }
2255c523858385176c33a7456bb84035de78552d22dMarc Blank
2265c523858385176c33a7456bb84035de78552d22dMarc Blank        // Generate a UID that mixes a "stable" device UID with the email address
2275c523858385176c33a7456bb84035de78552d22dMarc Blank        try {
2285c523858385176c33a7456bb84035de78552d22dMarc Blank            String devUID = Preferences.getPreferences(context).getDeviceUID();
2295c523858385176c33a7456bb84035de78552d22dMarc Blank            MessageDigest messageDigest;
2305c523858385176c33a7456bb84035de78552d22dMarc Blank            messageDigest = MessageDigest.getInstance("SHA-1");
2315c523858385176c33a7456bb84035de78552d22dMarc Blank            messageDigest.update(userName.getBytes());
2325c523858385176c33a7456bb84035de78552d22dMarc Blank            messageDigest.update(devUID.getBytes());
2335c523858385176c33a7456bb84035de78552d22dMarc Blank            byte[] uid = messageDigest.digest();
2345c523858385176c33a7456bb84035de78552d22dMarc Blank            String hexUid = Base64.encodeToString(uid, Base64.NO_WRAP);
2355c523858385176c33a7456bb84035de78552d22dMarc Blank            id.append(" \"AGUID\" \"");
2365c523858385176c33a7456bb84035de78552d22dMarc Blank            id.append(hexUid);
2375c523858385176c33a7456bb84035de78552d22dMarc Blank            id.append('\"');
2385c523858385176c33a7456bb84035de78552d22dMarc Blank        } catch (NoSuchAlgorithmException e) {
239560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "couldn't obtain SHA-1 hash for device UID");
2405c523858385176c33a7456bb84035de78552d22dMarc Blank        }
2415c523858385176c33a7456bb84035de78552d22dMarc Blank        return id.toString();
2425c523858385176c33a7456bb84035de78552d22dMarc Blank    }
2435c523858385176c33a7456bb84035de78552d22dMarc Blank
2445c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
2455c523858385176c33a7456bb84035de78552d22dMarc Blank     * Helper function that actually builds the static part of the IMAP ID string.  This is
2465c523858385176c33a7456bb84035de78552d22dMarc Blank     * separated from getImapId for testability.  There is no escaping or encoding in IMAP ID so
2475c523858385176c33a7456bb84035de78552d22dMarc Blank     * any rogue chars must be filtered here.
2485c523858385176c33a7456bb84035de78552d22dMarc Blank     *
2495c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param packageName context.getPackageName()
2505c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param version Build.VERSION.RELEASE
2515c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param codeName Build.VERSION.CODENAME
2525c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param model Build.MODEL
2535c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param id Build.ID
2545c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param vendor Build.MANUFACTURER
2555c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param networkOperator TelephonyManager.getNetworkOperatorName()
2565c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return the static (never changes) portion of the IMAP ID
2575c523858385176c33a7456bb84035de78552d22dMarc Blank     */
2585c523858385176c33a7456bb84035de78552d22dMarc Blank    @VisibleForTesting
2595c523858385176c33a7456bb84035de78552d22dMarc Blank    static String makeCommonImapId(String packageName, String version,
2605c523858385176c33a7456bb84035de78552d22dMarc Blank            String codeName, String model, String id, String vendor, String networkOperator) {
2615c523858385176c33a7456bb84035de78552d22dMarc Blank
2625c523858385176c33a7456bb84035de78552d22dMarc Blank        // Before building up IMAP ID string, pre-filter the input strings for "legal" chars
2635c523858385176c33a7456bb84035de78552d22dMarc Blank        // This is using a fairly arbitrary char set intended to pass through most reasonable
2645c523858385176c33a7456bb84035de78552d22dMarc Blank        // version, model, and vendor strings: a-z A-Z 0-9 - _ + = ; : . , / <space>
2655c523858385176c33a7456bb84035de78552d22dMarc Blank        // The most important thing is *not* to pass parens, quotes, or CRLF, which would break
2665c523858385176c33a7456bb84035de78552d22dMarc Blank        // the format of the IMAP ID list.
2675c523858385176c33a7456bb84035de78552d22dMarc Blank        Pattern p = Pattern.compile("[^a-zA-Z0-9-_\\+=;:\\.,/ ]");
2685c523858385176c33a7456bb84035de78552d22dMarc Blank        packageName = p.matcher(packageName).replaceAll("");
2695c523858385176c33a7456bb84035de78552d22dMarc Blank        version = p.matcher(version).replaceAll("");
2705c523858385176c33a7456bb84035de78552d22dMarc Blank        codeName = p.matcher(codeName).replaceAll("");
2715c523858385176c33a7456bb84035de78552d22dMarc Blank        model = p.matcher(model).replaceAll("");
2725c523858385176c33a7456bb84035de78552d22dMarc Blank        id = p.matcher(id).replaceAll("");
2735c523858385176c33a7456bb84035de78552d22dMarc Blank        vendor = p.matcher(vendor).replaceAll("");
2745c523858385176c33a7456bb84035de78552d22dMarc Blank        networkOperator = p.matcher(networkOperator).replaceAll("");
2755c523858385176c33a7456bb84035de78552d22dMarc Blank
2765c523858385176c33a7456bb84035de78552d22dMarc Blank        // "name" "com.android.email"
27742a4dbbf93eb5b0e45a831c65116682791765d02Tony Mantler        StringBuilder sb = new StringBuilder("\"name\" \"");
2785c523858385176c33a7456bb84035de78552d22dMarc Blank        sb.append(packageName);
2795c523858385176c33a7456bb84035de78552d22dMarc Blank        sb.append("\"");
2805c523858385176c33a7456bb84035de78552d22dMarc Blank
2815c523858385176c33a7456bb84035de78552d22dMarc Blank        // "os" "android"
2825c523858385176c33a7456bb84035de78552d22dMarc Blank        sb.append(" \"os\" \"android\"");
2835c523858385176c33a7456bb84035de78552d22dMarc Blank
2845c523858385176c33a7456bb84035de78552d22dMarc Blank        // "os-version" "version; build-id"
2855c523858385176c33a7456bb84035de78552d22dMarc Blank        sb.append(" \"os-version\" \"");
2865c523858385176c33a7456bb84035de78552d22dMarc Blank        if (version.length() > 0) {
2875c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append(version);
2885c523858385176c33a7456bb84035de78552d22dMarc Blank        } else {
2895c523858385176c33a7456bb84035de78552d22dMarc Blank            // default to "1.0"
2905c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append("1.0");
2915c523858385176c33a7456bb84035de78552d22dMarc Blank        }
2925c523858385176c33a7456bb84035de78552d22dMarc Blank        // add the build ID or build #
2935c523858385176c33a7456bb84035de78552d22dMarc Blank        if (id.length() > 0) {
2945c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append("; ");
2955c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append(id);
2965c523858385176c33a7456bb84035de78552d22dMarc Blank        }
2975c523858385176c33a7456bb84035de78552d22dMarc Blank        sb.append("\"");
2985c523858385176c33a7456bb84035de78552d22dMarc Blank
2995c523858385176c33a7456bb84035de78552d22dMarc Blank        // "vendor" "the vendor"
3005c523858385176c33a7456bb84035de78552d22dMarc Blank        if (vendor.length() > 0) {
3015c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append(" \"vendor\" \"");
3025c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append(vendor);
3035c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append("\"");
3045c523858385176c33a7456bb84035de78552d22dMarc Blank        }
3055c523858385176c33a7456bb84035de78552d22dMarc Blank
3065c523858385176c33a7456bb84035de78552d22dMarc Blank        // "x-android-device-model" the device model (on release builds only)
3075c523858385176c33a7456bb84035de78552d22dMarc Blank        if ("REL".equals(codeName)) {
3085c523858385176c33a7456bb84035de78552d22dMarc Blank            if (model.length() > 0) {
3095c523858385176c33a7456bb84035de78552d22dMarc Blank                sb.append(" \"x-android-device-model\" \"");
3105c523858385176c33a7456bb84035de78552d22dMarc Blank                sb.append(model);
3115c523858385176c33a7456bb84035de78552d22dMarc Blank                sb.append("\"");
3125c523858385176c33a7456bb84035de78552d22dMarc Blank            }
3135c523858385176c33a7456bb84035de78552d22dMarc Blank        }
3145c523858385176c33a7456bb84035de78552d22dMarc Blank
3155c523858385176c33a7456bb84035de78552d22dMarc Blank        // "x-android-mobile-net-operator" "name of network operator"
3165c523858385176c33a7456bb84035de78552d22dMarc Blank        if (networkOperator.length() > 0) {
3175c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append(" \"x-android-mobile-net-operator\" \"");
3185c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append(networkOperator);
3195c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append("\"");
3205c523858385176c33a7456bb84035de78552d22dMarc Blank        }
3215c523858385176c33a7456bb84035de78552d22dMarc Blank
3225c523858385176c33a7456bb84035de78552d22dMarc Blank        return sb.toString();
3235c523858385176c33a7456bb84035de78552d22dMarc Blank    }
3245c523858385176c33a7456bb84035de78552d22dMarc Blank
3255c523858385176c33a7456bb84035de78552d22dMarc Blank
3265c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
3275c523858385176c33a7456bb84035de78552d22dMarc Blank    public Folder getFolder(String name) {
3285c523858385176c33a7456bb84035de78552d22dMarc Blank        return new ImapFolder(this, name);
3295c523858385176c33a7456bb84035de78552d22dMarc Blank    }
3305c523858385176c33a7456bb84035de78552d22dMarc Blank
3315c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
3325c523858385176c33a7456bb84035de78552d22dMarc Blank     * Creates a mailbox hierarchy out of the flat data provided by the server.
3335c523858385176c33a7456bb84035de78552d22dMarc Blank     */
3345c523858385176c33a7456bb84035de78552d22dMarc Blank    @VisibleForTesting
3355c523858385176c33a7456bb84035de78552d22dMarc Blank    static void createHierarchy(HashMap<String, ImapFolder> mailboxes) {
3365c523858385176c33a7456bb84035de78552d22dMarc Blank        Set<String> pathnames = mailboxes.keySet();
3375c523858385176c33a7456bb84035de78552d22dMarc Blank        for (String path : pathnames) {
3385c523858385176c33a7456bb84035de78552d22dMarc Blank            final ImapFolder folder = mailboxes.get(path);
3395c523858385176c33a7456bb84035de78552d22dMarc Blank            final Mailbox mailbox = folder.mMailbox;
3405c523858385176c33a7456bb84035de78552d22dMarc Blank            int delimiterIdx = mailbox.mServerId.lastIndexOf(mailbox.mDelimiter);
3415c523858385176c33a7456bb84035de78552d22dMarc Blank            long parentKey = Mailbox.NO_MAILBOX;
342e3cf91af61fed0b35ef8cbb5790c39a143af7470Tony Mantler            String parentPath = null;
3435c523858385176c33a7456bb84035de78552d22dMarc Blank            if (delimiterIdx != -1) {
344e3cf91af61fed0b35ef8cbb5790c39a143af7470Tony Mantler                parentPath = path.substring(0, delimiterIdx);
34599f9ead3efeba6f0ca2d2b09bf8eb1aca8759b3eMartin Hibdon                if (ImapConstants.INBOX.equalsIgnoreCase(parentPath)) {
34699f9ead3efeba6f0ca2d2b09bf8eb1aca8759b3eMartin Hibdon                    // The Inbox is added as a special case, and always in all caps. In reality,
34799f9ead3efeba6f0ca2d2b09bf8eb1aca8759b3eMartin Hibdon                    // it might not be in all caps, this folder's parent path might have mixed case.
34899f9ead3efeba6f0ca2d2b09bf8eb1aca8759b3eMartin Hibdon                    parentPath = ImapConstants.INBOX;
34999f9ead3efeba6f0ca2d2b09bf8eb1aca8759b3eMartin Hibdon                }
3505c523858385176c33a7456bb84035de78552d22dMarc Blank                final ImapFolder parentFolder = mailboxes.get(parentPath);
3515c523858385176c33a7456bb84035de78552d22dMarc Blank                final Mailbox parentMailbox = (parentFolder == null) ? null : parentFolder.mMailbox;
3525c523858385176c33a7456bb84035de78552d22dMarc Blank                if (parentMailbox != null) {
3535c523858385176c33a7456bb84035de78552d22dMarc Blank                    parentKey = parentMailbox.mId;
3545c523858385176c33a7456bb84035de78552d22dMarc Blank                    parentMailbox.mFlags
3555c523858385176c33a7456bb84035de78552d22dMarc Blank                            |= (Mailbox.FLAG_HAS_CHILDREN | Mailbox.FLAG_CHILDREN_VISIBLE);
3565c523858385176c33a7456bb84035de78552d22dMarc Blank                }
3575c523858385176c33a7456bb84035de78552d22dMarc Blank            }
3585c523858385176c33a7456bb84035de78552d22dMarc Blank            mailbox.mParentKey = parentKey;
359e3cf91af61fed0b35ef8cbb5790c39a143af7470Tony Mantler            mailbox.mParentServerId = parentPath;
3605c523858385176c33a7456bb84035de78552d22dMarc Blank        }
3615c523858385176c33a7456bb84035de78552d22dMarc Blank    }
3625c523858385176c33a7456bb84035de78552d22dMarc Blank
3635c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
3645c523858385176c33a7456bb84035de78552d22dMarc Blank     * Creates a {@link Folder} and associated {@link Mailbox}. If the folder does not already
3655c523858385176c33a7456bb84035de78552d22dMarc Blank     * exist in the local database, a new row will immediately be created in the mailbox table.
3665c523858385176c33a7456bb84035de78552d22dMarc Blank     * Otherwise, the existing row will be used. Any changes to existing rows, will not be stored
3675c523858385176c33a7456bb84035de78552d22dMarc Blank     * to the database immediately.
3685c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param accountId The ID of the account the mailbox is to be associated with
3695c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param mailboxPath The path of the mailbox to add
3705c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param delimiter A path delimiter. May be {@code null} if there is no delimiter.
3715c523858385176c33a7456bb84035de78552d22dMarc Blank     * @param selectable If {@code true}, the mailbox can be selected and used to store messages.
372bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein     * @param mailbox If not null, mailbox is used instead of querying for the Mailbox.
3735c523858385176c33a7456bb84035de78552d22dMarc Blank     */
3745c523858385176c33a7456bb84035de78552d22dMarc Blank    private ImapFolder addMailbox(Context context, long accountId, String mailboxPath,
375bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein            char delimiter, boolean selectable, Mailbox mailbox) {
37642a4dbbf93eb5b0e45a831c65116682791765d02Tony Mantler        // TODO: pass in the mailbox type, or do a proper lookup here
37742a4dbbf93eb5b0e45a831c65116682791765d02Tony Mantler        final int mailboxType;
378bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein        if (mailbox == null) {
37942a4dbbf93eb5b0e45a831c65116682791765d02Tony Mantler            mailboxType = LegacyConversions.inferMailboxTypeFromName(context, mailboxPath);
380bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein            mailbox = Mailbox.getMailboxForPath(context, accountId, mailboxPath);
38142a4dbbf93eb5b0e45a831c65116682791765d02Tony Mantler        } else {
38242a4dbbf93eb5b0e45a831c65116682791765d02Tony Mantler            mailboxType = mailbox.mType;
383bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein        }
38442a4dbbf93eb5b0e45a831c65116682791765d02Tony Mantler        final ImapFolder folder = (ImapFolder) getFolder(mailboxPath);
3855c523858385176c33a7456bb84035de78552d22dMarc Blank        if (mailbox.isSaved()) {
3865c523858385176c33a7456bb84035de78552d22dMarc Blank            // existing mailbox
3875c523858385176c33a7456bb84035de78552d22dMarc Blank            // mailbox retrieved from database; save hash _before_ updating fields
3885c523858385176c33a7456bb84035de78552d22dMarc Blank            folder.mHash = mailbox.getHashes();
3895c523858385176c33a7456bb84035de78552d22dMarc Blank        }
39042a4dbbf93eb5b0e45a831c65116682791765d02Tony Mantler        updateMailbox(mailbox, accountId, mailboxPath, delimiter, selectable, mailboxType);
3915c523858385176c33a7456bb84035de78552d22dMarc Blank        if (folder.mHash == null) {
3925c523858385176c33a7456bb84035de78552d22dMarc Blank            // new mailbox
3935c523858385176c33a7456bb84035de78552d22dMarc Blank            // save hash after updating. allows tracking changes if the mailbox is saved
3945c523858385176c33a7456bb84035de78552d22dMarc Blank            // outside of #saveMailboxList()
3955c523858385176c33a7456bb84035de78552d22dMarc Blank            folder.mHash = mailbox.getHashes();
3965c523858385176c33a7456bb84035de78552d22dMarc Blank            // We must save this here to make sure we have a valid ID for later
39779cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon
39879cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            // This is a newly created folder from the server. By definition, if it came from
39979cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            // the server, it can be synched. We need to set the uiSyncStatus so that the UI
40079cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            // will not try to display the empty state until the sync completes.
40179cb83cad1ebc24864fd59542bd1509f67881ebaMartin Hibdon            mailbox.mUiSyncStatus = EmailContent.SYNC_STATUS_INITIAL_SYNC_NEEDED;
4025c523858385176c33a7456bb84035de78552d22dMarc Blank            mailbox.save(mContext);
4035c523858385176c33a7456bb84035de78552d22dMarc Blank        }
4045c523858385176c33a7456bb84035de78552d22dMarc Blank        folder.mMailbox = mailbox;
4055c523858385176c33a7456bb84035de78552d22dMarc Blank        return folder;
4065c523858385176c33a7456bb84035de78552d22dMarc Blank    }
4075c523858385176c33a7456bb84035de78552d22dMarc Blank
4085c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
4095c523858385176c33a7456bb84035de78552d22dMarc Blank     * Persists the folders in the given list.
4105c523858385176c33a7456bb84035de78552d22dMarc Blank     */
4115c523858385176c33a7456bb84035de78552d22dMarc Blank    private static void saveMailboxList(Context context, HashMap<String, ImapFolder> folderMap) {
4125c523858385176c33a7456bb84035de78552d22dMarc Blank        for (ImapFolder imapFolder : folderMap.values()) {
4135c523858385176c33a7456bb84035de78552d22dMarc Blank            imapFolder.save(context);
4145c523858385176c33a7456bb84035de78552d22dMarc Blank        }
4155c523858385176c33a7456bb84035de78552d22dMarc Blank    }
4165c523858385176c33a7456bb84035de78552d22dMarc Blank
4175c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
4185c523858385176c33a7456bb84035de78552d22dMarc Blank    public Folder[] updateFolders() throws MessagingException {
419529bdeaec1d19b035dddde204c0c7074ba8ea80fMartin Hibdon        // TODO: There is nothing that ever closes this connection. Trouble is, it's not exactly
420529bdeaec1d19b035dddde204c0c7074ba8ea80fMartin Hibdon        // clear when we should close it, we'd like to keep it open until we're really done
421529bdeaec1d19b035dddde204c0c7074ba8ea80fMartin Hibdon        // using it.
4225c523858385176c33a7456bb84035de78552d22dMarc Blank        ImapConnection connection = getConnection();
4235c523858385176c33a7456bb84035de78552d22dMarc Blank        try {
424e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            final HashMap<String, ImapFolder> mailboxes = new HashMap<String, ImapFolder>();
4255c523858385176c33a7456bb84035de78552d22dMarc Blank            // Establish a connection to the IMAP server; if necessary
4265c523858385176c33a7456bb84035de78552d22dMarc Blank            // This ensures a valid prefix if the prefix is automatically set by the server
4275c523858385176c33a7456bb84035de78552d22dMarc Blank            connection.executeSimpleCommand(ImapConstants.NOOP);
4285c523858385176c33a7456bb84035de78552d22dMarc Blank            String imapCommand = ImapConstants.LIST + " \"\" \"*\"";
4295c523858385176c33a7456bb84035de78552d22dMarc Blank            if (mPathPrefix != null) {
4305c523858385176c33a7456bb84035de78552d22dMarc Blank                imapCommand = ImapConstants.LIST + " \"\" \"" + mPathPrefix + "*\"";
4315c523858385176c33a7456bb84035de78552d22dMarc Blank            }
4325c523858385176c33a7456bb84035de78552d22dMarc Blank            List<ImapResponse> responses = connection.executeSimpleCommand(imapCommand);
4335c523858385176c33a7456bb84035de78552d22dMarc Blank            for (ImapResponse response : responses) {
4345c523858385176c33a7456bb84035de78552d22dMarc Blank                // S: * LIST (\Noselect) "/" ~/Mail/foo
4355c523858385176c33a7456bb84035de78552d22dMarc Blank                if (response.isDataResponse(0, ImapConstants.LIST)) {
4365c523858385176c33a7456bb84035de78552d22dMarc Blank                    // Get folder name.
4375c523858385176c33a7456bb84035de78552d22dMarc Blank                    ImapString encodedFolder = response.getStringOrEmpty(3);
4385c523858385176c33a7456bb84035de78552d22dMarc Blank                    if (encodedFolder.isEmpty()) continue;
4395c523858385176c33a7456bb84035de78552d22dMarc Blank
4405c523858385176c33a7456bb84035de78552d22dMarc Blank                    String folderName = decodeFolderName(encodedFolder.getString(), mPathPrefix);
441bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein
4425c523858385176c33a7456bb84035de78552d22dMarc Blank                    if (ImapConstants.INBOX.equalsIgnoreCase(folderName)) continue;
4435c523858385176c33a7456bb84035de78552d22dMarc Blank
4445c523858385176c33a7456bb84035de78552d22dMarc Blank                    // Parse attributes.
4455c523858385176c33a7456bb84035de78552d22dMarc Blank                    boolean selectable =
4465c523858385176c33a7456bb84035de78552d22dMarc Blank                        !response.getListOrEmpty(1).contains(ImapConstants.FLAG_NO_SELECT);
4475c523858385176c33a7456bb84035de78552d22dMarc Blank                    String delimiter = response.getStringOrEmpty(2).getString();
4485c523858385176c33a7456bb84035de78552d22dMarc Blank                    char delimiterChar = '\0';
4495c523858385176c33a7456bb84035de78552d22dMarc Blank                    if (!TextUtils.isEmpty(delimiter)) {
4505c523858385176c33a7456bb84035de78552d22dMarc Blank                        delimiterChar = delimiter.charAt(0);
4515c523858385176c33a7456bb84035de78552d22dMarc Blank                    }
452bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein                    ImapFolder folder = addMailbox(
453bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein                            mContext, mAccount.mId, folderName, delimiterChar, selectable, null);
4545c523858385176c33a7456bb84035de78552d22dMarc Blank                    mailboxes.put(folderName, folder);
4555c523858385176c33a7456bb84035de78552d22dMarc Blank                }
4565c523858385176c33a7456bb84035de78552d22dMarc Blank            }
4572192bf01e0df1285718b53f6b77d635f4e8c65caYu Ping Hu
4582192bf01e0df1285718b53f6b77d635f4e8c65caYu Ping Hu            // In order to properly map INBOX -> Inbox, handle it as a special case.
459bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein            final Mailbox inbox =
460bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein                    Mailbox.restoreMailboxOfType(mContext, mAccount.mId, Mailbox.TYPE_INBOX);
461bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein            final ImapFolder newFolder = addMailbox(
462bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein                    mContext, mAccount.mId, inbox.mServerId, '\0', true /*selectable*/, inbox);
463bc7cd75e9a8652bfa873007bcd12db2a140c7d73Andrew Sapperstein            mailboxes.put(ImapConstants.INBOX, newFolder);
4642192bf01e0df1285718b53f6b77d635f4e8c65caYu Ping Hu
4655c523858385176c33a7456bb84035de78552d22dMarc Blank            createHierarchy(mailboxes);
4665c523858385176c33a7456bb84035de78552d22dMarc Blank            saveMailboxList(mContext, mailboxes);
46742a4dbbf93eb5b0e45a831c65116682791765d02Tony Mantler            return mailboxes.values().toArray(new Folder[mailboxes.size()]);
4685c523858385176c33a7456bb84035de78552d22dMarc Blank        } catch (IOException ioe) {
4695c523858385176c33a7456bb84035de78552d22dMarc Blank            connection.close();
470e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            throw new MessagingException("Unable to get folder list", ioe);
4715c523858385176c33a7456bb84035de78552d22dMarc Blank        } catch (AuthenticationFailedException afe) {
4725c523858385176c33a7456bb84035de78552d22dMarc Blank            // We do NOT want this connection pooled, or we will continue to send NOOP and SELECT
4735c523858385176c33a7456bb84035de78552d22dMarc Blank            // commands to the server
4745c523858385176c33a7456bb84035de78552d22dMarc Blank            connection.destroyResponses();
4755c523858385176c33a7456bb84035de78552d22dMarc Blank            connection = null;
4765c523858385176c33a7456bb84035de78552d22dMarc Blank            throw afe;
4775c523858385176c33a7456bb84035de78552d22dMarc Blank        } finally {
4785c523858385176c33a7456bb84035de78552d22dMarc Blank            if (connection != null) {
479e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                // We keep our connection out of the pool as long as we are using it, then
480e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                // put it back into the pool so it can be reused.
4815c523858385176c33a7456bb84035de78552d22dMarc Blank                poolConnection(connection);
4825c523858385176c33a7456bb84035de78552d22dMarc Blank            }
4835c523858385176c33a7456bb84035de78552d22dMarc Blank        }
4845c523858385176c33a7456bb84035de78552d22dMarc Blank    }
4855c523858385176c33a7456bb84035de78552d22dMarc Blank
4865c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
4875c523858385176c33a7456bb84035de78552d22dMarc Blank    public Bundle checkSettings() throws MessagingException {
4885c523858385176c33a7456bb84035de78552d22dMarc Blank        int result = MessagingException.NO_ERROR;
4895c523858385176c33a7456bb84035de78552d22dMarc Blank        Bundle bundle = new Bundle();
490e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        // TODO: why doesn't this use getConnection()? I guess this is only done during setup,
491e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        // so there's need to look for a pooled connection?
492e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        // But then why doesn't it use poolConnection() after it's done?
493e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        ImapConnection connection = new ImapConnection(this);
4945c523858385176c33a7456bb84035de78552d22dMarc Blank        try {
4955c523858385176c33a7456bb84035de78552d22dMarc Blank            connection.open();
4965c523858385176c33a7456bb84035de78552d22dMarc Blank            connection.close();
4975c523858385176c33a7456bb84035de78552d22dMarc Blank        } catch (IOException ioe) {
4985c523858385176c33a7456bb84035de78552d22dMarc Blank            bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE, ioe.getMessage());
4995c523858385176c33a7456bb84035de78552d22dMarc Blank            result = MessagingException.IOERROR;
5005c523858385176c33a7456bb84035de78552d22dMarc Blank        } finally {
5015c523858385176c33a7456bb84035de78552d22dMarc Blank            connection.destroyResponses();
5025c523858385176c33a7456bb84035de78552d22dMarc Blank        }
5035c523858385176c33a7456bb84035de78552d22dMarc Blank        bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, result);
5045c523858385176c33a7456bb84035de78552d22dMarc Blank        return bundle;
5055c523858385176c33a7456bb84035de78552d22dMarc Blank    }
5065c523858385176c33a7456bb84035de78552d22dMarc Blank
5075c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
5085c523858385176c33a7456bb84035de78552d22dMarc Blank     * Returns whether or not the prefix has been set by the user. This can be determined by
5095c523858385176c33a7456bb84035de78552d22dMarc Blank     * the fact that the prefix is set, but, the path separator is not set.
5105c523858385176c33a7456bb84035de78552d22dMarc Blank     */
5115c523858385176c33a7456bb84035de78552d22dMarc Blank    boolean isUserPrefixSet() {
5125c523858385176c33a7456bb84035de78552d22dMarc Blank        return TextUtils.isEmpty(mPathSeparator) && !TextUtils.isEmpty(mPathPrefix);
5135c523858385176c33a7456bb84035de78552d22dMarc Blank    }
5145c523858385176c33a7456bb84035de78552d22dMarc Blank
5155c523858385176c33a7456bb84035de78552d22dMarc Blank    /** Sets the path separator */
5165c523858385176c33a7456bb84035de78552d22dMarc Blank    void setPathSeparator(String pathSeparator) {
5175c523858385176c33a7456bb84035de78552d22dMarc Blank        mPathSeparator = pathSeparator;
5185c523858385176c33a7456bb84035de78552d22dMarc Blank    }
5195c523858385176c33a7456bb84035de78552d22dMarc Blank
5205c523858385176c33a7456bb84035de78552d22dMarc Blank    /** Sets the prefix */
5215c523858385176c33a7456bb84035de78552d22dMarc Blank    void setPathPrefix(String pathPrefix) {
5225c523858385176c33a7456bb84035de78552d22dMarc Blank        mPathPrefix = pathPrefix;
5235c523858385176c33a7456bb84035de78552d22dMarc Blank    }
5245c523858385176c33a7456bb84035de78552d22dMarc Blank
5255c523858385176c33a7456bb84035de78552d22dMarc Blank    /** Gets the context for this store */
5265c523858385176c33a7456bb84035de78552d22dMarc Blank    Context getContext() {
5275c523858385176c33a7456bb84035de78552d22dMarc Blank        return mContext;
5285c523858385176c33a7456bb84035de78552d22dMarc Blank    }
5295c523858385176c33a7456bb84035de78552d22dMarc Blank
5305c523858385176c33a7456bb84035de78552d22dMarc Blank    /** Returns a clone of the transport associated with this store. */
5315c523858385176c33a7456bb84035de78552d22dMarc Blank    MailTransport cloneTransport() {
5325c523858385176c33a7456bb84035de78552d22dMarc Blank        return mTransport.clone();
5335c523858385176c33a7456bb84035de78552d22dMarc Blank    }
5345c523858385176c33a7456bb84035de78552d22dMarc Blank
5355c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
5365c523858385176c33a7456bb84035de78552d22dMarc Blank     * Fixes the path prefix, if necessary. The path prefix must always end with the
5375c523858385176c33a7456bb84035de78552d22dMarc Blank     * path separator.
5385c523858385176c33a7456bb84035de78552d22dMarc Blank     */
5395c523858385176c33a7456bb84035de78552d22dMarc Blank    void ensurePrefixIsValid() {
5405c523858385176c33a7456bb84035de78552d22dMarc Blank        // Make sure the path prefix ends with the path separator
5415c523858385176c33a7456bb84035de78552d22dMarc Blank        if (!TextUtils.isEmpty(mPathPrefix) && !TextUtils.isEmpty(mPathSeparator)) {
5425c523858385176c33a7456bb84035de78552d22dMarc Blank            if (!mPathPrefix.endsWith(mPathSeparator)) {
5435c523858385176c33a7456bb84035de78552d22dMarc Blank                mPathPrefix = mPathPrefix + mPathSeparator;
5445c523858385176c33a7456bb84035de78552d22dMarc Blank            }
5455c523858385176c33a7456bb84035de78552d22dMarc Blank        }
5465c523858385176c33a7456bb84035de78552d22dMarc Blank    }
5475c523858385176c33a7456bb84035de78552d22dMarc Blank
5485c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
5495c523858385176c33a7456bb84035de78552d22dMarc Blank     * Gets a connection if one is available from the pool, or creates a new one if not.
5505c523858385176c33a7456bb84035de78552d22dMarc Blank     */
5515c523858385176c33a7456bb84035de78552d22dMarc Blank    ImapConnection getConnection() {
552e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        // TODO Why would we ever have (or need to have) more than one active connection?
553e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        // TODO We set new username/password each time, but we don't actually close the transport
554e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        // when we do this. So if that information has changed, this connection will fail.
55542a4dbbf93eb5b0e45a831c65116682791765d02Tony Mantler        ImapConnection connection;
5565c523858385176c33a7456bb84035de78552d22dMarc Blank        while ((connection = mConnectionPool.poll()) != null) {
5575c523858385176c33a7456bb84035de78552d22dMarc Blank            try {
558e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                connection.setStore(this);
5595c523858385176c33a7456bb84035de78552d22dMarc Blank                connection.executeSimpleCommand(ImapConstants.NOOP);
5605c523858385176c33a7456bb84035de78552d22dMarc Blank                break;
5615c523858385176c33a7456bb84035de78552d22dMarc Blank            } catch (MessagingException e) {
5625c523858385176c33a7456bb84035de78552d22dMarc Blank                // Fall through
5635c523858385176c33a7456bb84035de78552d22dMarc Blank            } catch (IOException e) {
5645c523858385176c33a7456bb84035de78552d22dMarc Blank                // Fall through
5655c523858385176c33a7456bb84035de78552d22dMarc Blank            }
5665c523858385176c33a7456bb84035de78552d22dMarc Blank            connection.close();
5675c523858385176c33a7456bb84035de78552d22dMarc Blank        }
568e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon
5695c523858385176c33a7456bb84035de78552d22dMarc Blank        if (connection == null) {
570e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            connection = new ImapConnection(this);
5715c523858385176c33a7456bb84035de78552d22dMarc Blank        }
5725c523858385176c33a7456bb84035de78552d22dMarc Blank        return connection;
5735c523858385176c33a7456bb84035de78552d22dMarc Blank    }
5745c523858385176c33a7456bb84035de78552d22dMarc Blank
5755c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
5765c523858385176c33a7456bb84035de78552d22dMarc Blank     * Save a {@link ImapConnection} in the pool for reuse. Any responses associated with the
5775c523858385176c33a7456bb84035de78552d22dMarc Blank     * connection are destroyed before adding the connection to the pool.
5785c523858385176c33a7456bb84035de78552d22dMarc Blank     */
5795c523858385176c33a7456bb84035de78552d22dMarc Blank    void poolConnection(ImapConnection connection) {
5805c523858385176c33a7456bb84035de78552d22dMarc Blank        if (connection != null) {
5815c523858385176c33a7456bb84035de78552d22dMarc Blank            connection.destroyResponses();
5825c523858385176c33a7456bb84035de78552d22dMarc Blank            mConnectionPool.add(connection);
5835c523858385176c33a7456bb84035de78552d22dMarc Blank        }
5845c523858385176c33a7456bb84035de78552d22dMarc Blank    }
5855c523858385176c33a7456bb84035de78552d22dMarc Blank
5865c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
5875c523858385176c33a7456bb84035de78552d22dMarc Blank     * Prepends the folder name with the given prefix and UTF-7 encodes it.
5885c523858385176c33a7456bb84035de78552d22dMarc Blank     */
5895c523858385176c33a7456bb84035de78552d22dMarc Blank    static String encodeFolderName(String name, String prefix) {
5905c523858385176c33a7456bb84035de78552d22dMarc Blank        // do NOT add the prefix to the special name "INBOX"
5915c523858385176c33a7456bb84035de78552d22dMarc Blank        if (ImapConstants.INBOX.equalsIgnoreCase(name)) return name;
5925c523858385176c33a7456bb84035de78552d22dMarc Blank
5935c523858385176c33a7456bb84035de78552d22dMarc Blank        // Prepend prefix
5945c523858385176c33a7456bb84035de78552d22dMarc Blank        if (prefix != null) {
5955c523858385176c33a7456bb84035de78552d22dMarc Blank            name = prefix + name;
5965c523858385176c33a7456bb84035de78552d22dMarc Blank        }
5975c523858385176c33a7456bb84035de78552d22dMarc Blank
5985c523858385176c33a7456bb84035de78552d22dMarc Blank        // TODO bypass the conversion if name doesn't have special char.
5995c523858385176c33a7456bb84035de78552d22dMarc Blank        ByteBuffer bb = MODIFIED_UTF_7_CHARSET.encode(name);
6005c523858385176c33a7456bb84035de78552d22dMarc Blank        byte[] b = new byte[bb.limit()];
6015c523858385176c33a7456bb84035de78552d22dMarc Blank        bb.get(b);
6025c523858385176c33a7456bb84035de78552d22dMarc Blank
6035c523858385176c33a7456bb84035de78552d22dMarc Blank        return Utility.fromAscii(b);
6045c523858385176c33a7456bb84035de78552d22dMarc Blank    }
6055c523858385176c33a7456bb84035de78552d22dMarc Blank
6065c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
6075c523858385176c33a7456bb84035de78552d22dMarc Blank     * UTF-7 decodes the folder name and removes the given path prefix.
6085c523858385176c33a7456bb84035de78552d22dMarc Blank     */
6095c523858385176c33a7456bb84035de78552d22dMarc Blank    static String decodeFolderName(String name, String prefix) {
6105c523858385176c33a7456bb84035de78552d22dMarc Blank        // TODO bypass the conversion if name doesn't have special char.
6115c523858385176c33a7456bb84035de78552d22dMarc Blank        String folder;
6125c523858385176c33a7456bb84035de78552d22dMarc Blank        folder = MODIFIED_UTF_7_CHARSET.decode(ByteBuffer.wrap(Utility.toAscii(name))).toString();
6135c523858385176c33a7456bb84035de78552d22dMarc Blank        if ((prefix != null) && folder.startsWith(prefix)) {
6145c523858385176c33a7456bb84035de78552d22dMarc Blank            folder = folder.substring(prefix.length());
6155c523858385176c33a7456bb84035de78552d22dMarc Blank        }
6165c523858385176c33a7456bb84035de78552d22dMarc Blank        return folder;
6175c523858385176c33a7456bb84035de78552d22dMarc Blank    }
6185c523858385176c33a7456bb84035de78552d22dMarc Blank
6195c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
6205c523858385176c33a7456bb84035de78552d22dMarc Blank     * Returns UIDs of Messages joined with "," as the separator.
6215c523858385176c33a7456bb84035de78552d22dMarc Blank     */
6225c523858385176c33a7456bb84035de78552d22dMarc Blank    static String joinMessageUids(Message[] messages) {
6235c523858385176c33a7456bb84035de78552d22dMarc Blank        StringBuilder sb = new StringBuilder();
6245c523858385176c33a7456bb84035de78552d22dMarc Blank        boolean notFirst = false;
6255c523858385176c33a7456bb84035de78552d22dMarc Blank        for (Message m : messages) {
6265c523858385176c33a7456bb84035de78552d22dMarc Blank            if (notFirst) {
6275c523858385176c33a7456bb84035de78552d22dMarc Blank                sb.append(',');
6285c523858385176c33a7456bb84035de78552d22dMarc Blank            }
6295c523858385176c33a7456bb84035de78552d22dMarc Blank            sb.append(m.getUid());
6305c523858385176c33a7456bb84035de78552d22dMarc Blank            notFirst = true;
6315c523858385176c33a7456bb84035de78552d22dMarc Blank        }
6325c523858385176c33a7456bb84035de78552d22dMarc Blank        return sb.toString();
6335c523858385176c33a7456bb84035de78552d22dMarc Blank    }
6345c523858385176c33a7456bb84035de78552d22dMarc Blank
6355c523858385176c33a7456bb84035de78552d22dMarc Blank    static class ImapMessage extends MimeMessage {
6365c523858385176c33a7456bb84035de78552d22dMarc Blank        ImapMessage(String uid, ImapFolder folder) {
6375c523858385176c33a7456bb84035de78552d22dMarc Blank            mUid = uid;
6385c523858385176c33a7456bb84035de78552d22dMarc Blank            mFolder = folder;
6395c523858385176c33a7456bb84035de78552d22dMarc Blank        }
6405c523858385176c33a7456bb84035de78552d22dMarc Blank
6415c523858385176c33a7456bb84035de78552d22dMarc Blank        public void setSize(int size) {
6425c523858385176c33a7456bb84035de78552d22dMarc Blank            mSize = size;
6435c523858385176c33a7456bb84035de78552d22dMarc Blank        }
6445c523858385176c33a7456bb84035de78552d22dMarc Blank
6455c523858385176c33a7456bb84035de78552d22dMarc Blank        @Override
6465c523858385176c33a7456bb84035de78552d22dMarc Blank        public void parse(InputStream in) throws IOException, MessagingException {
6475c523858385176c33a7456bb84035de78552d22dMarc Blank            super.parse(in);
6485c523858385176c33a7456bb84035de78552d22dMarc Blank        }
6495c523858385176c33a7456bb84035de78552d22dMarc Blank
6505c523858385176c33a7456bb84035de78552d22dMarc Blank        public void setFlagInternal(Flag flag, boolean set) throws MessagingException {
6515c523858385176c33a7456bb84035de78552d22dMarc Blank            super.setFlag(flag, set);
6525c523858385176c33a7456bb84035de78552d22dMarc Blank        }
6535c523858385176c33a7456bb84035de78552d22dMarc Blank
6545c523858385176c33a7456bb84035de78552d22dMarc Blank        @Override
6555c523858385176c33a7456bb84035de78552d22dMarc Blank        public void setFlag(Flag flag, boolean set) throws MessagingException {
6565c523858385176c33a7456bb84035de78552d22dMarc Blank            super.setFlag(flag, set);
6575c523858385176c33a7456bb84035de78552d22dMarc Blank            mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set);
6585c523858385176c33a7456bb84035de78552d22dMarc Blank        }
6595c523858385176c33a7456bb84035de78552d22dMarc Blank    }
6605c523858385176c33a7456bb84035de78552d22dMarc Blank
6615c523858385176c33a7456bb84035de78552d22dMarc Blank    static class ImapException extends MessagingException {
6625c523858385176c33a7456bb84035de78552d22dMarc Blank        private static final long serialVersionUID = 1L;
6635c523858385176c33a7456bb84035de78552d22dMarc Blank
664a2f1da2bdc127558c5e2e63eb7bc94df563b7d83Tony Mantler        private final String mStatus;
6650dffe3afd7a2fdfb394573aa0d8d06dd90e9fe12James Lemieux        private final String mAlertText;
6660dffe3afd7a2fdfb394573aa0d8d06dd90e9fe12James Lemieux        private final String mResponseCode;
6675c523858385176c33a7456bb84035de78552d22dMarc Blank
668a2f1da2bdc127558c5e2e63eb7bc94df563b7d83Tony Mantler        public ImapException(String message, String status, String alertText,
669a2f1da2bdc127558c5e2e63eb7bc94df563b7d83Tony Mantler                String responseCode) {
6705c523858385176c33a7456bb84035de78552d22dMarc Blank            super(message);
671a2f1da2bdc127558c5e2e63eb7bc94df563b7d83Tony Mantler            mStatus = status;
6725c523858385176c33a7456bb84035de78552d22dMarc Blank            mAlertText = alertText;
6730dffe3afd7a2fdfb394573aa0d8d06dd90e9fe12James Lemieux            mResponseCode = responseCode;
6745c523858385176c33a7456bb84035de78552d22dMarc Blank        }
6755c523858385176c33a7456bb84035de78552d22dMarc Blank
676a2f1da2bdc127558c5e2e63eb7bc94df563b7d83Tony Mantler        public String getStatus() {
677a2f1da2bdc127558c5e2e63eb7bc94df563b7d83Tony Mantler            return mStatus;
678a2f1da2bdc127558c5e2e63eb7bc94df563b7d83Tony Mantler        }
679a2f1da2bdc127558c5e2e63eb7bc94df563b7d83Tony Mantler
6805c523858385176c33a7456bb84035de78552d22dMarc Blank        public String getAlertText() {
6815c523858385176c33a7456bb84035de78552d22dMarc Blank            return mAlertText;
6825c523858385176c33a7456bb84035de78552d22dMarc Blank        }
6835c523858385176c33a7456bb84035de78552d22dMarc Blank
6840dffe3afd7a2fdfb394573aa0d8d06dd90e9fe12James Lemieux        public String getResponseCode() {
6850dffe3afd7a2fdfb394573aa0d8d06dd90e9fe12James Lemieux            return mResponseCode;
6865c523858385176c33a7456bb84035de78552d22dMarc Blank        }
6875c523858385176c33a7456bb84035de78552d22dMarc Blank    }
688ff1ee36cb5f767804fdb62e1f1c6c724bac72e12Martin Hibdon
689ff1ee36cb5f767804fdb62e1f1c6c724bac72e12Martin Hibdon    public void closeConnections() {
6900dffe3afd7a2fdfb394573aa0d8d06dd90e9fe12James Lemieux        ImapConnection connection;
691ff1ee36cb5f767804fdb62e1f1c6c724bac72e12Martin Hibdon        while ((connection = mConnectionPool.poll()) != null) {
692ff1ee36cb5f767804fdb62e1f1c6c724bac72e12Martin Hibdon            connection.close();
693ff1ee36cb5f767804fdb62e1f1c6c724bac72e12Martin Hibdon        }
694ff1ee36cb5f767804fdb62e1f1c6c724bac72e12Martin Hibdon    }
6955c523858385176c33a7456bb84035de78552d22dMarc Blank}
696