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 * {
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 @@
<init>(java.lang.String);
<init>(java.lang.String,java.lang.String);
*** parseAndPack(java.lang.String);
*** legacyUnpack(java.lang.String);
}
-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
* 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;

View File

@ -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();

View File

@ -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<? extends android.app.Activity> setting = sender.getSettingActivityClass();
showOutgoing = (setting != null);

View File

@ -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<? extends android.app.Activity> setting = sender.getSettingActivityClass();
if (setting != null) {

View File

@ -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,23 +100,54 @@ 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);
String senderKey = getSenderKey(context, account);
Sender sender = sSenders.get(senderKey);
if (sender == null) {
context = context.getApplicationContext();
sender = findSender(context, R.xml.senders_product, uri);
Context appContext = context.getApplicationContext();
sender = findSender(appContext, R.xml.senders_product, account);
if (sender == null) {
sender = findSender(context, R.xml.senders, uri);
sender = findSender(appContext, R.xml.senders, account);
}
if (sender != null) {
sSenders.put(uri, sender);
sSenders.put(senderKey, sender);
}
}
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;

View File

@ -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,58 +52,46 @@ 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];
}
}
}
/**
* For testing only. Injects a different transport. The transport should already be set

View File

@ -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);
}
/**