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