diff --git a/proguard.flags b/proguard.flags index 7ed8d21de..0f6c90aaf 100644 --- a/proguard.flags +++ b/proguard.flags @@ -10,12 +10,7 @@ } -keepclasseswithmembers class * { - public *** newInstance(android.content.Context, java.lang.String); -} - -# TODO remove after converting Sender#instantiateSender() to use Account instead of URI --keepclasseswithmembers class * { - public *** newInstance(java.lang.String, android.content.Context, com.android.email.mail.Store$PersistentDataCallbacks); + public *** newInstance(com.android.emailcommon.provider.EmailContent$Account, android.content.Context); } -keepclasseswithmembers class * { @@ -60,7 +55,6 @@ (java.lang.String); (java.lang.String,java.lang.String); *** parseAndPack(java.lang.String); - *** legacyUnpack(java.lang.String); } -keepclasseswithmembers class com.android.email.SecurityPolicy { diff --git a/src/com/android/email/MessagingController.java b/src/com/android/email/MessagingController.java index 8976b177c..5bd0c7542 100644 --- a/src/com/android/email/MessagingController.java +++ b/src/com/android/email/MessagingController.java @@ -226,20 +226,17 @@ public class MessagingController implements Runnable { } /** - * Lists folders that are available locally and remotely. This method calls - * listFoldersCallback for local folders before it returns, and then for - * remote folders at some later point. If there are no local folders - * includeRemote is forced by this method. This method should be called from - * a Thread as it may take several seconds to list the local folders. + * Asynchronously synchronize the folder list. If the specified {@link MessagingListener} + * is not {@code null}, it must have been previously added to the set of listeners using the + * {@link #addListener(MessagingListener)}. Otherwise, no actions will be performed. * * TODO this needs to cache the remote folder list * TODO break out an inner listFoldersSynchronized which could simplify checkMail * - * @param account - * @param listener - * @throws MessagingException + * @param accountId ID of the account for which to list the folders + * @param listener A listener to notify */ - public void listFolders(final long accountId, MessagingListener listener) { + void listFolders(final long accountId, MessagingListener listener) { final EmailContent.Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId); if (account == null) { @@ -1173,7 +1170,7 @@ public class MessagingController implements Runnable { * @param accountIdArgs */ private void processPendingUploadsSynchronous(EmailContent.Account account, - ContentResolver resolver, String[] accountIdArgs) throws MessagingException { + ContentResolver resolver, String[] accountIdArgs) { // Find the Sent folder (since that's all we're uploading for now Cursor mailboxes = resolver.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION, MailboxColumns.ACCOUNT_KEY + "=?" @@ -1878,10 +1875,6 @@ public class MessagingController implements Runnable { /** * Attempts to load the attachment specified by id from the given account and message. - * @param account - * @param message - * @param part - * @param listener */ public void loadAttachment(final long accountId, final long messageId, final long mailboxId, final long attachmentId, MessagingListener listener, final boolean background) { @@ -1993,10 +1986,8 @@ public class MessagingController implements Runnable { } /** - * Attempt to send any messages that are sitting in the Outbox. - * - * @param account - * @param listener + * Attempt to send all messages sitting in the given account's outbox. Optionally, + * if the server requires it, the message will be moved to the given sent folder. */ public void sendPendingMessagesSynchronous(final EmailContent.Account account, long sentFolderId) { @@ -2019,7 +2010,7 @@ public class MessagingController implements Runnable { // 3. do one-time setup of the Sender & other stuff mListeners.sendPendingMessagesStarted(account.mId, -1); - Sender sender = Sender.getInstance(mContext, account.getSenderUri(mContext)); + Sender sender = Sender.getInstance(mContext, account); Store remoteStore = Store.getInstance(account, mContext, null); boolean requireMoveMessageToSentFolder = remoteStore.requireCopyMessageToSentFolder(); ContentValues moveToSentValues = null; diff --git a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java index 2313d75d2..2ca214b50 100644 --- a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java +++ b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java @@ -392,7 +392,6 @@ public class AccountCheckSettingsFragment extends Fragment { final int mMode; final Account mAccount; final String mStoreHost; - final String mSenderUri; final String mCheckEmail; final String mCheckPassword; @@ -406,7 +405,6 @@ public class AccountCheckSettingsFragment extends Fragment { mMode = mode; mAccount = checkAccount; mStoreHost = checkAccount.mHostAuthRecv.mAddress; - mSenderUri = checkAccount.getSenderUri(mContext); mCheckEmail = checkAccount.mEmailAddress; mCheckPassword = checkAccount.mHostAuthRecv.mPassword; } @@ -475,7 +473,7 @@ public class AccountCheckSettingsFragment extends Fragment { if (isCancelled()) return null; Log.d(Logging.LOG_TAG, "Begin check of outgoing email settings"); publishProgress(STATE_CHECK_OUTGOING); - Sender sender = Sender.getInstance(mContext, mSenderUri); + Sender sender = Sender.getInstance(mContext, mAccount); sender.close(); sender.open(); sender.close(); diff --git a/src/com/android/email/activity/setup/AccountSettingsFragment.java b/src/com/android/email/activity/setup/AccountSettingsFragment.java index f82da2890..2bd98b3b2 100644 --- a/src/com/android/email/activity/setup/AccountSettingsFragment.java +++ b/src/com/android/email/activity/setup/AccountSettingsFragment.java @@ -521,7 +521,7 @@ public class AccountSettingsFragment extends PreferenceFragment { Preference prefOutgoing = findPreference(PREFERENCE_OUTGOING); boolean showOutgoing = true; try { - Sender sender = Sender.getInstance(mContext, mAccount.getSenderUri(mContext)); + Sender sender = Sender.getInstance(mContext, mAccount); if (sender != null) { Class setting = sender.getSettingActivityClass(); showOutgoing = (setting != null); diff --git a/src/com/android/email/activity/setup/AccountSettingsXL.java b/src/com/android/email/activity/setup/AccountSettingsXL.java index 9f2df3189..1558f184e 100644 --- a/src/com/android/email/activity/setup/AccountSettingsXL.java +++ b/src/com/android/email/activity/setup/AccountSettingsXL.java @@ -641,7 +641,7 @@ public class AccountSettingsXL extends PreferenceActivity { */ public void onOutgoingSettings(Account account) { try { - Sender sender = Sender.getInstance(getApplication(), account.getSenderUri(this)); + Sender sender = Sender.getInstance(getApplication(), account); if (sender != null) { Class setting = sender.getSettingActivityClass(); if (setting != null) { diff --git a/src/com/android/email/mail/Sender.java b/src/com/android/email/mail/Sender.java index 43a4b778c..ddd85700f 100644 --- a/src/com/android/email/mail/Sender.java +++ b/src/com/android/email/mail/Sender.java @@ -19,11 +19,14 @@ package com.android.email.mail; import com.android.email.R; import com.android.emailcommon.Logging; import com.android.emailcommon.mail.MessagingException; +import com.android.emailcommon.provider.EmailContent.Account; +import com.android.emailcommon.provider.EmailContent.HostAuth; import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.content.res.XmlResourceParser; +import android.text.TextUtils; import android.util.Log; import java.io.IOException; @@ -38,29 +41,30 @@ public abstract class Sender { * Static named constructor. It should be overrode by extending class. * Because this method will be called through reflection, it can not be protected. */ - public static Sender newInstance(Context context, String uri) + public static Sender newInstance(Context context, Account account) throws MessagingException { - throw new MessagingException("Sender.newInstance: Unknown scheme in " + uri); + throw new MessagingException("Sender.newInstance: Unknown scheme in " + + account.mDisplayName); } - private static Sender instantiateSender(Context context, String className, String uri) + private static Sender instantiateSender(Context context, String className, Account account) throws MessagingException { Object o = null; try { Class c = Class.forName(className); // and invoke "newInstance" class method and instantiate sender object. java.lang.reflect.Method m = - c.getMethod("newInstance", Context.class, String.class); - o = m.invoke(null, context, uri); + c.getMethod("newInstance", Account.class, Context.class); + o = m.invoke(null, account, context); } catch (Exception e) { Log.d(Logging.LOG_TAG, String.format( - "exception %s invoking %s.newInstance.(Context, String) method for %s", - e.toString(), className, uri)); - throw new MessagingException("can not instantiate Sender object for " + uri); + "exception %s invoking method %s#newInstance(Account, Context) for %s", + e.toString(), className, account.mDisplayName)); + throw new MessagingException("can not instantiate Sender for " + account.mDisplayName); } if (!(o instanceof Sender)) { throw new MessagingException( - uri + ": " + className + " create incompatible object"); + account.mDisplayName + ": " + className + " create incompatible object"); } return (Sender) o; } @@ -68,22 +72,23 @@ public abstract class Sender { /** * Find Sender implementation consulting with sender.xml file. */ - private static Sender findSender(Context context, int resourceId, String uri) + private static Sender findSender(Context context, int resourceId, Account account) throws MessagingException { Sender sender = null; try { XmlResourceParser xml = context.getResources().getXml(resourceId); int xmlEventType; + HostAuth sendAuth = account.getOrCreateHostAuthSend(context); // walk through senders.xml file. while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { if (xmlEventType == XmlResourceParser.START_TAG && "sender".equals(xml.getName())) { - String scheme = xml.getAttributeValue(null, "scheme"); - if (uri.startsWith(scheme)) { + String xmlScheme = xml.getAttributeValue(null, "scheme"); + if (sendAuth.mProtocol != null && sendAuth.mProtocol.startsWith(xmlScheme)) { // found sender entry whose scheme is matched with uri. // then load sender class. String className = xml.getAttributeValue(null, "class"); - sender = instantiateSender(context, className, uri); + sender = instantiateSender(context, className, account); } } } @@ -95,26 +100,57 @@ public abstract class Sender { return sender; } - public synchronized static Sender getInstance(Context context, String uri) + /** + * Gets a unique key for the given account. + * @throws MessagingException If the account is not setup properly (i.e. there is no address + * or login) + */ + private static String getSenderKey(Context context, Account account) throws MessagingException { + final StringBuffer key = new StringBuffer(); + final HostAuth sendAuth = account.getOrCreateHostAuthSend(context); + if (sendAuth.mAddress == null) { + throw new MessagingException("Cannot find sender for account " + account.mDisplayName); + } + final String address = sendAuth.mAddress.trim(); + if (TextUtils.isEmpty(address)) { + throw new MessagingException("Cannot find sender for account " + account.mDisplayName); + } + key.append(address); + if (sendAuth.mLogin != null) { + key.append(sendAuth.mLogin.trim()); + } + return key.toString(); + } + + /** + * Get an instance of a mail sender for the given account. The account must be valid (i.e. has + * at least an outgoing server name). + * + * @param account The account of the sender. + * @return an initialized sender of the appropriate class + * @throws MessagingException If the sender cannot be obtained or if the account is invalid. + */ + public synchronized static Sender getInstance(Context context, Account account) throws MessagingException { - Sender sender = sSenders.get(uri); - if (sender == null) { - context = context.getApplicationContext(); - sender = findSender(context, R.xml.senders_product, uri); - if (sender == null) { - sender = findSender(context, R.xml.senders, uri); - } + String senderKey = getSenderKey(context, account); + Sender sender = sSenders.get(senderKey); + if (sender == null) { + Context appContext = context.getApplicationContext(); + sender = findSender(appContext, R.xml.senders_product, account); + if (sender == null) { + sender = findSender(appContext, R.xml.senders, account); + } - if (sender != null) { - sSenders.put(uri, sender); - } - } + if (sender != null) { + sSenders.put(senderKey, sender); + } + } - if (sender == null) { - throw new MessagingException("Unable to locate an applicable Transport for " + uri); - } + if (sender == null) { + throw new MessagingException("Cannot find sender for account " + account.mDisplayName); + } - return sender; + return sender; } /** diff --git a/src/com/android/email/mail/transport/SmtpSender.java b/src/com/android/email/mail/transport/SmtpSender.java index b2cdead52..bbd3a0f59 100644 --- a/src/com/android/email/mail/transport/SmtpSender.java +++ b/src/com/android/email/mail/transport/SmtpSender.java @@ -25,6 +25,8 @@ import com.android.emailcommon.mail.Address; import com.android.emailcommon.mail.AuthenticationFailedException; import com.android.emailcommon.mail.CertificateValidationException; import com.android.emailcommon.mail.MessagingException; +import com.android.emailcommon.provider.EmailContent.Account; +import com.android.emailcommon.provider.EmailContent.HostAuth; import com.android.emailcommon.provider.EmailContent.Message; import android.content.Context; @@ -33,8 +35,6 @@ import android.util.Base64; import java.io.IOException; import java.net.InetAddress; -import java.net.URI; -import java.net.URISyntaxException; import javax.net.ssl.SSLException; @@ -52,56 +52,44 @@ public class SmtpSender extends Sender { /** * Static named constructor. */ - public static Sender newInstance(Context context, String uri) throws MessagingException { - return new SmtpSender(context, uri); + public static Sender newInstance(Account account, Context context) throws MessagingException { + return new SmtpSender(context, account); } /** - * Allowed formats for the Uri: - * smtp://user:password@server:port - * smtp+tls+://user:password@server:port - * smtp+tls+trustallcerts://user:password@server:port - * smtp+ssl+://user:password@server:port - * smtp+ssl+trustallcerts://user:password@server:port - * - * @param uriString the Uri containing information to configure this sender + * Creates a new sender for the given account. */ - @SuppressWarnings("deprecation") - private SmtpSender(Context context, String uriString) throws MessagingException { + private SmtpSender(Context context, Account account) throws MessagingException { mContext = context; - URI uri; - try { - uri = new URI(uriString); - } catch (URISyntaxException use) { - throw new MessagingException("Invalid SmtpTransport URI", use); - } - - String scheme = uri.getScheme(); - if (scheme == null || !scheme.startsWith("smtp")) { + HostAuth sendAuth = account.getOrCreateHostAuthSend(context); + if (sendAuth == null || !"smtp".equalsIgnoreCase(sendAuth.mProtocol)) { throw new MessagingException("Unsupported protocol"); } // defaults, which can be changed by security modifiers int connectionSecurity = Transport.CONNECTION_SECURITY_NONE; int defaultPort = 587; - // check for security modifiers and apply changes - if (scheme.contains("+ssl")) { + + // check for security flags and apply changes + if ((sendAuth.mFlags & HostAuth.FLAG_SSL) != 0) { connectionSecurity = Transport.CONNECTION_SECURITY_SSL; defaultPort = 465; - } else if (scheme.contains("+tls")) { + } else if ((sendAuth.mFlags & HostAuth.FLAG_TLS) != 0) { connectionSecurity = Transport.CONNECTION_SECURITY_TLS; } - boolean trustCertificates = scheme.contains("+trustallcerts"); - - mTransport = new MailTransport("SMTP"); - mTransport.setUri(uri, defaultPort); + boolean trustCertificates = ((sendAuth.mFlags & HostAuth.FLAG_TRUST_ALL) != 0); + int port = defaultPort; + if (sendAuth.mPort != HostAuth.PORT_UNKNOWN) { + port = sendAuth.mPort; + } + mTransport = new MailTransport("IMAP"); + mTransport.setHost(sendAuth.mAddress); + mTransport.setPort(port); mTransport.setSecurity(connectionSecurity, trustCertificates); - String[] userInfoParts = mTransport.getUserInfoParts(); + String[] userInfoParts = sendAuth.getLogin(); if (userInfoParts != null) { mUsername = userInfoParts[0]; - if (userInfoParts.length > 1) { - mPassword = userInfoParts[1]; - } + mPassword = userInfoParts[1]; } } diff --git a/tests/src/com/android/email/mail/transport/SmtpSenderUnitTests.java b/tests/src/com/android/email/mail/transport/SmtpSenderUnitTests.java index 540803304..d4b1c0a67 100644 --- a/tests/src/com/android/email/mail/transport/SmtpSenderUnitTests.java +++ b/tests/src/com/android/email/mail/transport/SmtpSenderUnitTests.java @@ -21,8 +21,10 @@ import com.android.email.mail.Transport; import com.android.email.provider.EmailProvider; import com.android.emailcommon.mail.Address; import com.android.emailcommon.mail.MessagingException; +import com.android.emailcommon.provider.EmailContent.Account; import com.android.emailcommon.provider.EmailContent.Attachment; import com.android.emailcommon.provider.EmailContent.Body; +import com.android.emailcommon.provider.EmailContent.HostAuth; import com.android.emailcommon.provider.EmailContent.Message; import org.apache.commons.io.IOUtils; @@ -73,9 +75,13 @@ public class SmtpSenderUnitTests extends AndroidTestCase { getContext()); mContext = getContext(); - // These are needed so we can get at the inner classes - mSender = (SmtpSender) SmtpSender.newInstance(mProviderContext, - "smtp://user:password@server:999"); + HostAuth testAuth = new HostAuth(); + Account testAccount = new Account(); + + testAuth.setLogin("user", "password"); + testAuth.setConnection("smtp", "server", 999); + testAccount.mHostAuthSend = testAuth; + mSender = (SmtpSender) SmtpSender.newInstance(testAccount, mProviderContext); } /**