Use an Account object to create a mail sender

Instead of boiling the account down to an unusable URI, just pass along
the Account object.

Change-Id: Ida408912de29734c8f4ed9cdf09a4d633dd03002
This commit is contained in:
Todd Kennedy 2011-04-20 08:04:46 -07:00
parent 031527a03e
commit daf869cf60
8 changed files with 110 additions and 97 deletions

View File

@ -10,12 +10,7 @@
} }
-keepclasseswithmembers class * { -keepclasseswithmembers class * {
public *** newInstance(android.content.Context, java.lang.String); public *** newInstance(com.android.emailcommon.provider.EmailContent$Account, android.content.Context);
}
# 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);
} }
-keepclasseswithmembers class * { -keepclasseswithmembers class * {
@ -60,7 +55,6 @@
<init>(java.lang.String); <init>(java.lang.String);
<init>(java.lang.String,java.lang.String); <init>(java.lang.String,java.lang.String);
*** parseAndPack(java.lang.String); *** parseAndPack(java.lang.String);
*** legacyUnpack(java.lang.String);
} }
-keepclasseswithmembers class com.android.email.SecurityPolicy { -keepclasseswithmembers class com.android.email.SecurityPolicy {

View File

@ -226,20 +226,17 @@ public class MessagingController implements Runnable {
} }
/** /**
* Lists folders that are available locally and remotely. This method calls * Asynchronously synchronize the folder list. If the specified {@link MessagingListener}
* listFoldersCallback for local folders before it returns, and then for * is not {@code null}, it must have been previously added to the set of listeners using the
* remote folders at some later point. If there are no local folders * {@link #addListener(MessagingListener)}. Otherwise, no actions will be performed.
* 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.
* *
* TODO this needs to cache the remote folder list * TODO this needs to cache the remote folder list
* TODO break out an inner listFoldersSynchronized which could simplify checkMail * TODO break out an inner listFoldersSynchronized which could simplify checkMail
* *
* @param account * @param accountId ID of the account for which to list the folders
* @param listener * @param listener A listener to notify
* @throws MessagingException
*/ */
public void listFolders(final long accountId, MessagingListener listener) { void listFolders(final long accountId, MessagingListener listener) {
final EmailContent.Account account = final EmailContent.Account account =
EmailContent.Account.restoreAccountWithId(mContext, accountId); EmailContent.Account.restoreAccountWithId(mContext, accountId);
if (account == null) { if (account == null) {
@ -1173,7 +1170,7 @@ public class MessagingController implements Runnable {
* @param accountIdArgs * @param accountIdArgs
*/ */
private void processPendingUploadsSynchronous(EmailContent.Account account, 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 // Find the Sent folder (since that's all we're uploading for now
Cursor mailboxes = resolver.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION, Cursor mailboxes = resolver.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION,
MailboxColumns.ACCOUNT_KEY + "=?" 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. * 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, public void loadAttachment(final long accountId, final long messageId, final long mailboxId,
final long attachmentId, MessagingListener listener, final boolean background) { 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. * 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.
* @param account
* @param listener
*/ */
public void sendPendingMessagesSynchronous(final EmailContent.Account account, public void sendPendingMessagesSynchronous(final EmailContent.Account account,
long sentFolderId) { long sentFolderId) {
@ -2019,7 +2010,7 @@ public class MessagingController implements Runnable {
// 3. do one-time setup of the Sender & other stuff // 3. do one-time setup of the Sender & other stuff
mListeners.sendPendingMessagesStarted(account.mId, -1); 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); Store remoteStore = Store.getInstance(account, mContext, null);
boolean requireMoveMessageToSentFolder = remoteStore.requireCopyMessageToSentFolder(); boolean requireMoveMessageToSentFolder = remoteStore.requireCopyMessageToSentFolder();
ContentValues moveToSentValues = null; ContentValues moveToSentValues = null;

View File

@ -392,7 +392,6 @@ public class AccountCheckSettingsFragment extends Fragment {
final int mMode; final int mMode;
final Account mAccount; final Account mAccount;
final String mStoreHost; final String mStoreHost;
final String mSenderUri;
final String mCheckEmail; final String mCheckEmail;
final String mCheckPassword; final String mCheckPassword;
@ -406,7 +405,6 @@ public class AccountCheckSettingsFragment extends Fragment {
mMode = mode; mMode = mode;
mAccount = checkAccount; mAccount = checkAccount;
mStoreHost = checkAccount.mHostAuthRecv.mAddress; mStoreHost = checkAccount.mHostAuthRecv.mAddress;
mSenderUri = checkAccount.getSenderUri(mContext);
mCheckEmail = checkAccount.mEmailAddress; mCheckEmail = checkAccount.mEmailAddress;
mCheckPassword = checkAccount.mHostAuthRecv.mPassword; mCheckPassword = checkAccount.mHostAuthRecv.mPassword;
} }
@ -475,7 +473,7 @@ public class AccountCheckSettingsFragment extends Fragment {
if (isCancelled()) return null; if (isCancelled()) return null;
Log.d(Logging.LOG_TAG, "Begin check of outgoing email settings"); Log.d(Logging.LOG_TAG, "Begin check of outgoing email settings");
publishProgress(STATE_CHECK_OUTGOING); publishProgress(STATE_CHECK_OUTGOING);
Sender sender = Sender.getInstance(mContext, mSenderUri); Sender sender = Sender.getInstance(mContext, mAccount);
sender.close(); sender.close();
sender.open(); sender.open();
sender.close(); sender.close();

View File

@ -521,7 +521,7 @@ public class AccountSettingsFragment extends PreferenceFragment {
Preference prefOutgoing = findPreference(PREFERENCE_OUTGOING); Preference prefOutgoing = findPreference(PREFERENCE_OUTGOING);
boolean showOutgoing = true; boolean showOutgoing = true;
try { try {
Sender sender = Sender.getInstance(mContext, mAccount.getSenderUri(mContext)); Sender sender = Sender.getInstance(mContext, mAccount);
if (sender != null) { if (sender != null) {
Class<? extends android.app.Activity> setting = sender.getSettingActivityClass(); Class<? extends android.app.Activity> setting = sender.getSettingActivityClass();
showOutgoing = (setting != null); showOutgoing = (setting != null);

View File

@ -641,7 +641,7 @@ public class AccountSettingsXL extends PreferenceActivity {
*/ */
public void onOutgoingSettings(Account account) { public void onOutgoingSettings(Account account) {
try { try {
Sender sender = Sender.getInstance(getApplication(), account.getSenderUri(this)); Sender sender = Sender.getInstance(getApplication(), account);
if (sender != null) { if (sender != null) {
Class<? extends android.app.Activity> setting = sender.getSettingActivityClass(); Class<? extends android.app.Activity> setting = sender.getSettingActivityClass();
if (setting != null) { if (setting != null) {

View File

@ -19,11 +19,14 @@ package com.android.email.mail;
import com.android.email.R; import com.android.email.R;
import com.android.emailcommon.Logging; import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.MessagingException; 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 org.xmlpull.v1.XmlPullParserException;
import android.content.Context; import android.content.Context;
import android.content.res.XmlResourceParser; import android.content.res.XmlResourceParser;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import java.io.IOException; import java.io.IOException;
@ -38,29 +41,30 @@ public abstract class Sender {
* Static named constructor. It should be overrode by extending class. * Static named constructor. It should be overrode by extending class.
* Because this method will be called through reflection, it can not be protected. * 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 { 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 { throws MessagingException {
Object o = null; Object o = null;
try { try {
Class<?> c = Class.forName(className); Class<?> c = Class.forName(className);
// and invoke "newInstance" class method and instantiate sender object. // and invoke "newInstance" class method and instantiate sender object.
java.lang.reflect.Method m = java.lang.reflect.Method m =
c.getMethod("newInstance", Context.class, String.class); c.getMethod("newInstance", Account.class, Context.class);
o = m.invoke(null, context, uri); o = m.invoke(null, account, context);
} catch (Exception e) { } catch (Exception e) {
Log.d(Logging.LOG_TAG, String.format( Log.d(Logging.LOG_TAG, String.format(
"exception %s invoking %s.newInstance.(Context, String) method for %s", "exception %s invoking method %s#newInstance(Account, Context) for %s",
e.toString(), className, uri)); e.toString(), className, account.mDisplayName));
throw new MessagingException("can not instantiate Sender object for " + uri); throw new MessagingException("can not instantiate Sender for " + account.mDisplayName);
} }
if (!(o instanceof Sender)) { if (!(o instanceof Sender)) {
throw new MessagingException( throw new MessagingException(
uri + ": " + className + " create incompatible object"); account.mDisplayName + ": " + className + " create incompatible object");
} }
return (Sender) o; return (Sender) o;
} }
@ -68,22 +72,23 @@ public abstract class Sender {
/** /**
* Find Sender implementation consulting with sender.xml file. * 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 { throws MessagingException {
Sender sender = null; Sender sender = null;
try { try {
XmlResourceParser xml = context.getResources().getXml(resourceId); XmlResourceParser xml = context.getResources().getXml(resourceId);
int xmlEventType; int xmlEventType;
HostAuth sendAuth = account.getOrCreateHostAuthSend(context);
// walk through senders.xml file. // walk through senders.xml file.
while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) {
if (xmlEventType == XmlResourceParser.START_TAG && if (xmlEventType == XmlResourceParser.START_TAG &&
"sender".equals(xml.getName())) { "sender".equals(xml.getName())) {
String scheme = xml.getAttributeValue(null, "scheme"); String xmlScheme = xml.getAttributeValue(null, "scheme");
if (uri.startsWith(scheme)) { if (sendAuth.mProtocol != null && sendAuth.mProtocol.startsWith(xmlScheme)) {
// found sender entry whose scheme is matched with uri. // found sender entry whose scheme is matched with uri.
// then load sender class. // then load sender class.
String className = xml.getAttributeValue(null, "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; 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 { throws MessagingException {
Sender sender = sSenders.get(uri); String senderKey = getSenderKey(context, account);
if (sender == null) { Sender sender = sSenders.get(senderKey);
context = context.getApplicationContext(); if (sender == null) {
sender = findSender(context, R.xml.senders_product, uri); Context appContext = context.getApplicationContext();
if (sender == null) { sender = findSender(appContext, R.xml.senders_product, account);
sender = findSender(context, R.xml.senders, uri); if (sender == null) {
} sender = findSender(appContext, R.xml.senders, account);
}
if (sender != null) { if (sender != null) {
sSenders.put(uri, sender); sSenders.put(senderKey, sender);
} }
} }
if (sender == null) { if (sender == null) {
throw new MessagingException("Unable to locate an applicable Transport for " + uri); throw new MessagingException("Cannot find sender for account " + account.mDisplayName);
} }
return sender; return sender;
} }
/** /**

View File

@ -25,6 +25,8 @@ import com.android.emailcommon.mail.Address;
import com.android.emailcommon.mail.AuthenticationFailedException; import com.android.emailcommon.mail.AuthenticationFailedException;
import com.android.emailcommon.mail.CertificateValidationException; import com.android.emailcommon.mail.CertificateValidationException;
import com.android.emailcommon.mail.MessagingException; 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 com.android.emailcommon.provider.EmailContent.Message;
import android.content.Context; import android.content.Context;
@ -33,8 +35,6 @@ import android.util.Base64;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
@ -52,56 +52,44 @@ public class SmtpSender extends Sender {
/** /**
* Static named constructor. * Static named constructor.
*/ */
public static Sender newInstance(Context context, String uri) throws MessagingException { public static Sender newInstance(Account account, Context context) throws MessagingException {
return new SmtpSender(context, uri); return new SmtpSender(context, account);
} }
/** /**
* Allowed formats for the Uri: * Creates a new sender for the given account.
* 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
*/ */
@SuppressWarnings("deprecation") private SmtpSender(Context context, Account account) throws MessagingException {
private SmtpSender(Context context, String uriString) throws MessagingException {
mContext = context; mContext = context;
URI uri; HostAuth sendAuth = account.getOrCreateHostAuthSend(context);
try { if (sendAuth == null || !"smtp".equalsIgnoreCase(sendAuth.mProtocol)) {
uri = new URI(uriString);
} catch (URISyntaxException use) {
throw new MessagingException("Invalid SmtpTransport URI", use);
}
String scheme = uri.getScheme();
if (scheme == null || !scheme.startsWith("smtp")) {
throw new MessagingException("Unsupported protocol"); throw new MessagingException("Unsupported protocol");
} }
// defaults, which can be changed by security modifiers // defaults, which can be changed by security modifiers
int connectionSecurity = Transport.CONNECTION_SECURITY_NONE; int connectionSecurity = Transport.CONNECTION_SECURITY_NONE;
int defaultPort = 587; 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; connectionSecurity = Transport.CONNECTION_SECURITY_SSL;
defaultPort = 465; defaultPort = 465;
} else if (scheme.contains("+tls")) { } else if ((sendAuth.mFlags & HostAuth.FLAG_TLS) != 0) {
connectionSecurity = Transport.CONNECTION_SECURITY_TLS; connectionSecurity = Transport.CONNECTION_SECURITY_TLS;
} }
boolean trustCertificates = scheme.contains("+trustallcerts"); boolean trustCertificates = ((sendAuth.mFlags & HostAuth.FLAG_TRUST_ALL) != 0);
int port = defaultPort;
mTransport = new MailTransport("SMTP"); if (sendAuth.mPort != HostAuth.PORT_UNKNOWN) {
mTransport.setUri(uri, defaultPort); port = sendAuth.mPort;
}
mTransport = new MailTransport("IMAP");
mTransport.setHost(sendAuth.mAddress);
mTransport.setPort(port);
mTransport.setSecurity(connectionSecurity, trustCertificates); mTransport.setSecurity(connectionSecurity, trustCertificates);
String[] userInfoParts = mTransport.getUserInfoParts(); String[] userInfoParts = sendAuth.getLogin();
if (userInfoParts != null) { if (userInfoParts != null) {
mUsername = userInfoParts[0]; mUsername = userInfoParts[0];
if (userInfoParts.length > 1) { mPassword = userInfoParts[1];
mPassword = userInfoParts[1];
}
} }
} }

View File

@ -21,8 +21,10 @@ import com.android.email.mail.Transport;
import com.android.email.provider.EmailProvider; import com.android.email.provider.EmailProvider;
import com.android.emailcommon.mail.Address; import com.android.emailcommon.mail.Address;
import com.android.emailcommon.mail.MessagingException; 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.Attachment;
import com.android.emailcommon.provider.EmailContent.Body; import com.android.emailcommon.provider.EmailContent.Body;
import com.android.emailcommon.provider.EmailContent.HostAuth;
import com.android.emailcommon.provider.EmailContent.Message; import com.android.emailcommon.provider.EmailContent.Message;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -73,9 +75,13 @@ public class SmtpSenderUnitTests extends AndroidTestCase {
getContext()); getContext());
mContext = getContext(); mContext = getContext();
// These are needed so we can get at the inner classes HostAuth testAuth = new HostAuth();
mSender = (SmtpSender) SmtpSender.newInstance(mProviderContext, Account testAccount = new Account();
"smtp://user:password@server:999");
testAuth.setLogin("user", "password");
testAuth.setConnection("smtp", "server", 999);
testAccount.mHostAuthSend = testAuth;
mSender = (SmtpSender) SmtpSender.newInstance(testAccount, mProviderContext);
} }
/** /**