Convert POP3 to service
* Remove MessagingController and (almost all of) MailService Change-Id: I8953b58b237de6a71fda770f1727bd94081fec55
This commit is contained in:
parent
d57e096b2c
commit
4f813fb129
|
@ -406,6 +406,17 @@
|
|||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.Pop3Service"
|
||||
android:enabled="true"
|
||||
android:permission="com.android.email.permission.ACCESS_PROVIDER"
|
||||
>
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="com.android.email.POP3_INTENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!--Required stanza to register the EasAuthenticatorService with AccountManager -->
|
||||
<service
|
||||
android:name=".service.EasAuthenticatorService"
|
||||
|
|
|
@ -444,6 +444,23 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
return (Integer)mReturn;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the service to send mail in the specified account's Outbox
|
||||
*
|
||||
* @param accountId the account whose outgoing mail should be sent
|
||||
*/
|
||||
@Override
|
||||
public void sendMail(final long accountId) throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException{
|
||||
if (mCallback != null) mService.setCallback(mCallback);
|
||||
mService.sendMail(accountId);
|
||||
}
|
||||
}, "sendMail");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return null;
|
||||
|
|
|
@ -57,4 +57,6 @@ interface IEmailService {
|
|||
|
||||
// API level 2
|
||||
int searchMessages(long accountId, in SearchParams params, long destMailboxId);
|
||||
|
||||
void sendMail(long accountId);
|
||||
}
|
||||
|
|
|
@ -16,33 +16,26 @@
|
|||
|
||||
package com.android.email;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.mail.store.Pop3Store.Pop3Message;
|
||||
import com.android.email.provider.AccountBackupRestore;
|
||||
import com.android.email.provider.Utilities;
|
||||
import com.android.email.service.EmailServiceUtils;
|
||||
import com.android.email.service.MailService;
|
||||
import com.android.emailcommon.Api;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.mail.AuthenticationFailedException;
|
||||
import com.android.emailcommon.mail.Folder.MessageRetrievalListener;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.EmailContent.Attachment;
|
||||
import com.android.emailcommon.provider.EmailContent.Body;
|
||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.Message;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
|
@ -74,12 +67,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
* to IMAP, POP3, and EAS by AttachmentDownloadService
|
||||
*/
|
||||
public class Controller {
|
||||
private static final String TAG = "Controller";
|
||||
private static Controller sInstance;
|
||||
private final Context mContext;
|
||||
private Context mProviderContext;
|
||||
private final MessagingController mLegacyController;
|
||||
private final LegacyListener mLegacyListener = new LegacyListener();
|
||||
private final ServiceCallback mServiceCallback = new ServiceCallback();
|
||||
private final HashSet<Result> mListeners = new HashSet<Result>();
|
||||
/*package*/ final ConcurrentHashMap<Long, Boolean> mLegacyControllerMap =
|
||||
|
@ -102,11 +92,6 @@ public class Controller {
|
|||
};
|
||||
private static final int MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID = 1;
|
||||
|
||||
private static final String[] BODY_SOURCE_KEY_PROJECTION =
|
||||
new String[] {Body.SOURCE_MESSAGE_KEY};
|
||||
private static final int BODY_SOURCE_KEY_COLUMN = 0;
|
||||
private static final String WHERE_MESSAGE_KEY = Body.MESSAGE_KEY + "=?";
|
||||
|
||||
private static final String MAILBOXES_FOR_ACCOUNT_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?";
|
||||
private static final String MAILBOXES_FOR_ACCOUNT_EXCEPT_ACCOUNT_MAILBOX_SELECTION =
|
||||
MAILBOXES_FOR_ACCOUNT_SELECTION + " AND " + MailboxColumns.TYPE + "!=" +
|
||||
|
@ -122,8 +107,6 @@ public class Controller {
|
|||
protected Controller(Context _context) {
|
||||
mContext = _context.getApplicationContext();
|
||||
mProviderContext = _context;
|
||||
mLegacyController = MessagingController.getInstance(mProviderContext, this);
|
||||
mLegacyController.addListener(mLegacyListener);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,16 +118,6 @@ public class Controller {
|
|||
mInUnitTests = inUnitTests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup for test. Mustn't be called for the regular {@link Controller}, as it's a
|
||||
* singleton and lives till the process finishes.
|
||||
*
|
||||
* <p>However, this method MUST be called for mock instances.
|
||||
*/
|
||||
public void cleanupForTest() {
|
||||
mLegacyController.removeListener(mLegacyListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates the singleton instance of Controller.
|
||||
*/
|
||||
|
@ -309,8 +282,8 @@ public class Controller {
|
|||
// Commit the message to the local store
|
||||
msg.save(mProviderContext);
|
||||
// Setup the rest of the message and mark it completely loaded
|
||||
mLegacyController.copyOneMessageToProvider(pop3Message, msg,
|
||||
Message.FLAG_LOADED_COMPLETE, mProviderContext);
|
||||
Utilities.copyOneMessageToProvider(mProviderContext, pop3Message, msg,
|
||||
Message.FLAG_LOADED_COMPLETE);
|
||||
// Restore the complete message and return it
|
||||
return Message.restoreMessageWithId(mProviderContext, msg.mId);
|
||||
} catch (MessagingException e) {
|
||||
|
@ -356,35 +329,12 @@ public class Controller {
|
|||
Log.d("updateMailboxList", "RemoteException" + e);
|
||||
}
|
||||
} else {
|
||||
// MessagingController implementation
|
||||
mLegacyController.listFolders(accountId, mLegacyListener);
|
||||
throw new IllegalStateException("No service for updateMailboxList?");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a remote update of a mailbox. For use by the timed service.
|
||||
*
|
||||
* Functionally this is quite similar to updateMailbox(), but it's a separate API and
|
||||
* separate callback in order to keep UI callbacks from affecting the service loop.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void serviceCheckMail(final long accountId, final long mailboxId, final long tag) {
|
||||
IEmailService service = getServiceForAccount(accountId);
|
||||
if (service != null) {
|
||||
mLegacyListener.checkMailFinished(mContext, accountId, mailboxId, tag);
|
||||
} else {
|
||||
// MessagingController implementation
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mLegacyController.checkMail(accountId, tag, mLegacyListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a remote update of a mailbox.
|
||||
*
|
||||
|
@ -392,7 +342,6 @@ public class Controller {
|
|||
* a simple message list. We should also at this point queue up a background task of
|
||||
* downloading some/all of the messages in this mailbox, but that should be interruptable.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void updateMailbox(final long accountId, final long mailboxId, boolean userRequest) {
|
||||
|
||||
IEmailService service = getServiceForAccount(accountId);
|
||||
|
@ -404,24 +353,9 @@ public class Controller {
|
|||
// is implemented for other protocols
|
||||
Log.d("updateMailbox", "RemoteException" + e);
|
||||
}
|
||||
} else {
|
||||
// MessagingController implementation
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO shouldn't be passing fully-build accounts & mailboxes into APIs
|
||||
Account account =
|
||||
Account.restoreAccountWithId(mProviderContext, accountId);
|
||||
Mailbox mailbox =
|
||||
Mailbox.restoreMailboxWithId(mProviderContext, mailboxId);
|
||||
if (account == null || mailbox == null ||
|
||||
mailbox.mType == Mailbox.TYPE_SEARCH) {
|
||||
return;
|
||||
}
|
||||
mLegacyController.synchronizeMailbox(account, mailbox, mLegacyListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("No service for loadMessageForView?");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -434,40 +368,29 @@ public class Controller {
|
|||
* @param messageId the message to load
|
||||
* @param callback the Controller callback by which results will be reported
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void loadMessageForView(final long messageId) {
|
||||
|
||||
// Split here for target type (Service or MessagingController)
|
||||
EmailServiceProxy service = getServiceForMessage(messageId);
|
||||
if (service != null && service.isRemote()) {
|
||||
// Get rid of this!!
|
||||
if (service.isRemote()) {
|
||||
// There is no service implementation, so we'll just jam the value, log the error,
|
||||
// and get out of here.
|
||||
Uri uri = ContentUris.withAppendedId(Message.CONTENT_URI, messageId);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(MessageColumns.FLAG_LOADED, Message.FLAG_LOADED_COMPLETE);
|
||||
mProviderContext.getContentResolver().update(uri, cv, null, null);
|
||||
Log.d(Logging.LOG_TAG, "Unexpected loadMessageForView() for service-based message.");
|
||||
Log.d(Logging.LOG_TAG, "Unexpected loadMessageForView() for remote service message.");
|
||||
final long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadMessageForViewCallback(null, accountId, messageId, 100);
|
||||
}
|
||||
}
|
||||
} else if (service != null) {
|
||||
// IMAP here for now
|
||||
} else {
|
||||
try {
|
||||
service.loadMore(messageId);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
} else {
|
||||
// MessagingController implementation
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mLegacyController.loadMessageForView(messageId, mLegacyListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -598,50 +521,12 @@ public class Controller {
|
|||
sendPendingMessages(accountId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void sendPendingMessagesSmtp(long accountId) {
|
||||
// for IMAP & POP only, (attempt to) send the message now
|
||||
final Account account =
|
||||
Account.restoreAccountWithId(mProviderContext, accountId);
|
||||
if (account == null) {
|
||||
return;
|
||||
}
|
||||
final long sentboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_SENT);
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mLegacyController.sendPendingMessages(account, sentboxId, mLegacyListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to send all pending messages for a given account
|
||||
*
|
||||
* @param accountId the account for which to send messages
|
||||
*/
|
||||
public void sendPendingMessages(long accountId) {
|
||||
// 1. make sure we even have an outbox, exit early if not
|
||||
final long outboxId =
|
||||
Mailbox.findMailboxOfType(mProviderContext, accountId, Mailbox.TYPE_OUTBOX);
|
||||
if (outboxId == Mailbox.NO_MAILBOX) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. dispatch as necessary
|
||||
IEmailService service = getServiceForAccount(accountId);
|
||||
if (service != null) {
|
||||
// Service implementation
|
||||
try {
|
||||
service.startSync(outboxId, false);
|
||||
} catch (RemoteException e) {
|
||||
// TODO Change exception handling to be consistent with however this method
|
||||
// is implemented for other protocols
|
||||
Log.d("updateMailbox", "RemoteException" + e);
|
||||
}
|
||||
} else {
|
||||
// MessagingController implementation
|
||||
sendPendingMessagesSmtp(accountId);
|
||||
private void sendPendingMessages(long accountId) {
|
||||
EmailServiceProxy service =
|
||||
EmailServiceUtils.getServiceForAccount(mContext, null, accountId);
|
||||
try {
|
||||
service.sendMail(accountId);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -718,7 +603,7 @@ public class Controller {
|
|||
mProviderContext.getContentResolver().update(uri, cv, null, null);
|
||||
// Trigger a refresh using the new, longer limit
|
||||
mailbox.mVisibleLimit += Email.VISIBLE_LIMIT_INCREMENT;
|
||||
mLegacyController.synchronizeMailbox(account, mailbox, mLegacyListener);
|
||||
//mLegacyController.synchronizeMailbox(account, mailbox, mLegacyListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1005,24 +890,13 @@ public class Controller {
|
|||
// TODO Change exception handling to be consistent with however this method
|
||||
// is implemented for other protocols
|
||||
Log.e("searchMessages", "RemoteException", e);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// This is the actual mailbox we'll be searching
|
||||
Mailbox actualMailbox = Mailbox.restoreMailboxWithId(mContext, searchParams.mMailboxId);
|
||||
if (actualMailbox == null) {
|
||||
Log.e(Logging.LOG_TAG, "Unable to find mailbox " + searchParams.mMailboxId
|
||||
+ " to search in with " + searchParams);
|
||||
return 0;
|
||||
}
|
||||
// Do the search
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Logging.LOG_TAG, "Search: " + searchParams.mFilter);
|
||||
}
|
||||
// Plumb this
|
||||
return 0;
|
||||
//return mLegacyController.searchMailbox(accountId, searchParams, searchMailboxId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private EmailServiceProxy getServiceForAccount(long accountId) {
|
||||
return EmailServiceUtils.getServiceForAccount(mContext, mCallbackProxy, accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1096,46 +970,6 @@ public class Controller {
|
|||
return getServiceForAccount(message.mAccountKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given account id, return a service proxy if applicable, or null.
|
||||
*
|
||||
* @param accountId the message of interest
|
||||
* @result service proxy, or null if n/a
|
||||
*/
|
||||
private EmailServiceProxy getServiceForAccount(long accountId) {
|
||||
if (isMessagingController(accountId)) return null;
|
||||
if (Account.getProtocol(mContext, accountId).equals(HostAuth.SCHEME_IMAP)) {
|
||||
return getImapEmailService();
|
||||
}
|
||||
return getExchangeEmailService();
|
||||
}
|
||||
|
||||
private EmailServiceProxy getExchangeEmailService() {
|
||||
return EmailServiceUtils.getExchangeService(mContext, mServiceCallback);
|
||||
}
|
||||
|
||||
private EmailServiceProxy getImapEmailService() {
|
||||
return EmailServiceUtils.getImapService(mContext, mServiceCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper to determine if legacy MessagingController should be used
|
||||
*/
|
||||
public boolean isMessagingController(Account account) {
|
||||
if (account == null) return false;
|
||||
return isMessagingController(account.mId);
|
||||
}
|
||||
|
||||
public boolean isMessagingController(long accountId) {
|
||||
Boolean isLegacyController = mLegacyControllerMap.get(accountId);
|
||||
if (isLegacyController == null) {
|
||||
String protocol = Account.getProtocol(mProviderContext, accountId);
|
||||
isLegacyController = (HostAuth.SCHEME_POP3.equals(protocol));
|
||||
mLegacyControllerMap.put(accountId, isLegacyController);
|
||||
}
|
||||
return isLegacyController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an account.
|
||||
*/
|
||||
|
@ -1177,7 +1011,6 @@ public class Controller {
|
|||
SecurityPolicy.getInstance(context).reducePolicies();
|
||||
Email.setServicesEnabledSync(context);
|
||||
Email.setNotifyUiAccountsChanged(true);
|
||||
MailService.actionReschedule(context);
|
||||
} catch (Exception e) {
|
||||
Log.w(Logging.LOG_TAG, "Exception while deleting account", e);
|
||||
}
|
||||
|
@ -1343,248 +1176,6 @@ public class Controller {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bridge to intercept {@link MessageRetrievalListener#loadAttachmentProgress} and
|
||||
* pass down to {@link Result}.
|
||||
*/
|
||||
public class MessageRetrievalListenerBridge implements MessageRetrievalListener {
|
||||
private final long mMessageId;
|
||||
private final long mAttachmentId;
|
||||
private final long mAccountId;
|
||||
|
||||
public MessageRetrievalListenerBridge(long messageId, long attachmentId) {
|
||||
mMessageId = messageId;
|
||||
mAttachmentId = attachmentId;
|
||||
mAccountId = Account.getAccountIdForMessageId(mProviderContext, mMessageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentProgress(int progress) {
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadAttachmentCallback(null, mAccountId, mMessageId, mAttachmentId,
|
||||
progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageRetrieved(com.android.emailcommon.mail.Message message) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for receiving callbacks from MessagingController and dealing with UI going
|
||||
* out of scope.
|
||||
*/
|
||||
public class LegacyListener extends MessagingListener {
|
||||
public LegacyListener() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listFoldersStarted(long accountId) {
|
||||
synchronized (mListeners) {
|
||||
for (Result l : mListeners) {
|
||||
l.updateMailboxListCallback(null, accountId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listFoldersFailed(long accountId, String message) {
|
||||
synchronized (mListeners) {
|
||||
for (Result l : mListeners) {
|
||||
l.updateMailboxListCallback(new MessagingException(message), accountId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listFoldersFinished(long accountId) {
|
||||
synchronized (mListeners) {
|
||||
for (Result l : mListeners) {
|
||||
l.updateMailboxListCallback(null, accountId, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void synchronizeMailboxStarted(long accountId, long mailboxId) {
|
||||
synchronized (mListeners) {
|
||||
for (Result l : mListeners) {
|
||||
l.updateMailboxCallback(null, accountId, mailboxId, 0, 0, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void synchronizeMailboxFinished(long accountId, long mailboxId,
|
||||
int totalMessagesInMailbox, int numNewMessages, ArrayList<Long> addedMessages) {
|
||||
synchronized (mListeners) {
|
||||
for (Result l : mListeners) {
|
||||
l.updateMailboxCallback(null, accountId, mailboxId, 100, numNewMessages,
|
||||
addedMessages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void synchronizeMailboxFailed(long accountId, long mailboxId, Exception e) {
|
||||
MessagingException me;
|
||||
if (e instanceof MessagingException) {
|
||||
me = (MessagingException) e;
|
||||
} else {
|
||||
me = new MessagingException(e.toString());
|
||||
}
|
||||
synchronized (mListeners) {
|
||||
for (Result l : mListeners) {
|
||||
l.updateMailboxCallback(me, accountId, mailboxId, 0, 0, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkMailStarted(Context context, long accountId, long tag) {
|
||||
synchronized (mListeners) {
|
||||
for (Result l : mListeners) {
|
||||
l.serviceCheckMailCallback(null, accountId, -1, 0, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkMailFinished(Context context, long accountId, long folderId, long tag) {
|
||||
synchronized (mListeners) {
|
||||
for (Result l : mListeners) {
|
||||
l.serviceCheckMailCallback(null, accountId, folderId, 100, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessageForViewStarted(long messageId) {
|
||||
final long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadMessageForViewCallback(null, accountId, messageId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessageForViewFinished(long messageId) {
|
||||
final long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadMessageForViewCallback(null, accountId, messageId, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessageForViewFailed(long messageId, String message) {
|
||||
final long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadMessageForViewCallback(new MessagingException(message),
|
||||
accountId, messageId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentStarted(long accountId, long messageId, long attachmentId,
|
||||
boolean requiresDownload) {
|
||||
try {
|
||||
mCallbackProxy.loadAttachmentStatus(messageId, attachmentId,
|
||||
EmailServiceStatus.IN_PROGRESS, 0);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadAttachmentCallback(null, accountId, messageId, attachmentId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentFinished(long accountId, long messageId, long attachmentId) {
|
||||
try {
|
||||
mCallbackProxy.loadAttachmentStatus(messageId, attachmentId,
|
||||
EmailServiceStatus.SUCCESS, 100);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadAttachmentCallback(null, accountId, messageId, attachmentId, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentFailed(long accountId, long messageId, long attachmentId,
|
||||
MessagingException me, boolean background) {
|
||||
try {
|
||||
// If the cause of the MessagingException is an IOException, we send a status of
|
||||
// CONNECTION_ERROR; in this case, AttachmentDownloadService will try again to
|
||||
// download the attachment. Otherwise, the error is considered non-recoverable.
|
||||
int status = EmailServiceStatus.ATTACHMENT_NOT_FOUND;
|
||||
if (me != null && me.getCause() instanceof IOException) {
|
||||
status = EmailServiceStatus.CONNECTION_ERROR;
|
||||
}
|
||||
mCallbackProxy.loadAttachmentStatus(messageId, attachmentId, status, 0);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
// TODO We are overloading the exception here. The UI listens for this
|
||||
// callback and displays a toast if the exception is not null. Since we
|
||||
// want to avoid displaying toast for background operations, we force
|
||||
// the exception to be null. This needs to be re-worked so the UI will
|
||||
// only receive (or at least pays attention to) responses for requests
|
||||
// it explicitly cares about. Then we would not need to overload the
|
||||
// exception parameter.
|
||||
listener.loadAttachmentCallback(background ? null : me, accountId, messageId,
|
||||
attachmentId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void sendPendingMessagesStarted(long accountId, long messageId) {
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.sendMailCallback(null, accountId, messageId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void sendPendingMessagesCompleted(long accountId) {
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.sendMailCallback(null, accountId, -1, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void sendPendingMessagesFailed(long accountId, long messageId,
|
||||
Exception reason) {
|
||||
MessagingException me;
|
||||
if (reason instanceof MessagingException) {
|
||||
me = (MessagingException) reason;
|
||||
} else {
|
||||
me = new MessagingException(reason.toString());
|
||||
}
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.sendMailCallback(me, accountId, messageId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Service callback for service operations
|
||||
*/
|
||||
|
@ -1764,8 +1355,7 @@ public class Controller {
|
|||
* Proxy that can be used to broadcast service callbacks; we currently use this only for
|
||||
* loadAttachment callbacks
|
||||
*/
|
||||
private final IEmailServiceCallback.Stub mCallbackProxy =
|
||||
new IEmailServiceCallback.Stub() {
|
||||
private final IEmailServiceCallback.Stub mCallbackProxy = new IEmailServiceCallback.Stub() {
|
||||
|
||||
/**
|
||||
* Broadcast a callback to the everyone that's registered
|
||||
|
@ -1799,147 +1389,23 @@ public class Controller {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageStatus(long messageId, String subject, int statusCode, int progress){
|
||||
public void syncMailboxListStatus(long accountId, int statusCode, int progress)
|
||||
throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessageStatus(long messageId, int statusCode, int progress){
|
||||
public void syncMailboxStatus(long mailboxId, int statusCode, int progress)
|
||||
throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxListStatus(long accountId, int statusCode, int progress) {
|
||||
public void sendMessageStatus(long messageId, String subject, int statusCode, int progress)
|
||||
throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxStatus(long mailboxId, int statusCode, int progress) {
|
||||
public void loadMessageStatus(long messageId, int statusCode, int progress)
|
||||
throws RemoteException {
|
||||
}
|
||||
};
|
||||
|
||||
public static class ControllerService extends Service {
|
||||
/**
|
||||
* Create our EmailService implementation here. For now, only loadAttachment is supported;
|
||||
* the intention, however, is to move more functionality to the service interface
|
||||
*/
|
||||
private final IEmailService.Stub mBinder = new IEmailService.Stub() {
|
||||
|
||||
@Override
|
||||
public Bundle validate(HostAuth hostAuth) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle autoDiscover(String userName, String password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSync(long mailboxId, boolean userRequest) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSync(long mailboxId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachment(long attachmentId, boolean background)
|
||||
throws RemoteException {
|
||||
Attachment att = Attachment.restoreAttachmentWithId(ControllerService.this,
|
||||
attachmentId);
|
||||
if (att != null) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "loadAttachment " + attachmentId + ": " + att.mFileName);
|
||||
}
|
||||
Message msg = Message.restoreMessageWithId(ControllerService.this,
|
||||
att.mMessageKey);
|
||||
if (msg != null) {
|
||||
// If the message is a forward and the attachment needs downloading, we need
|
||||
// to retrieve the message from the source, rather than from the message
|
||||
// itself
|
||||
if ((msg.mFlags & Message.FLAG_TYPE_FORWARD) != 0) {
|
||||
String[] cols = Utility.getRowColumns(ControllerService.this,
|
||||
Body.CONTENT_URI, BODY_SOURCE_KEY_PROJECTION, WHERE_MESSAGE_KEY,
|
||||
new String[] {Long.toString(msg.mId)});
|
||||
if (cols != null) {
|
||||
msg = Message.restoreMessageWithId(ControllerService.this,
|
||||
Long.parseLong(cols[BODY_SOURCE_KEY_COLUMN]));
|
||||
if (msg == null) {
|
||||
// TODO: We can try restoring from the deleted table here...
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
MessagingController legacyController = sInstance.mLegacyController;
|
||||
LegacyListener legacyListener = sInstance.mLegacyListener;
|
||||
legacyController.loadAttachment(msg.mAccountKey, msg.mId, msg.mMailboxKey,
|
||||
attachmentId, legacyListener, background);
|
||||
} else {
|
||||
// Send back the specific error status for this case
|
||||
sInstance.mCallbackProxy.loadAttachmentStatus(att.mMessageKey, attachmentId,
|
||||
EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFolderList(long accountId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hostChanged(long accountId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogging(int flags) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMeetingResponse(long messageId, int response) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMore(long messageId) {
|
||||
}
|
||||
|
||||
// The following three methods are not implemented in this version
|
||||
@Override
|
||||
public boolean createFolder(long accountId, String name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteFolder(long accountId, String name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameFolder(long accountId, String oldName, String newName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(IEmailServiceCallback cb) {
|
||||
sCallbackList.register(cb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccountPIMData(long accountId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int searchMessages(long accountId, SearchParams searchParams,
|
||||
long destMailboxId) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getApiLevel() {
|
||||
return Api.LEVEL;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import com.android.email.activity.MessageCompose;
|
|||
import com.android.email.activity.ShortcutPicker;
|
||||
import com.android.email.service.AttachmentDownloadService;
|
||||
import com.android.email.service.MailService;
|
||||
import com.android.email.widget.WidgetConfiguration;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.TempDirectory;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
|
@ -127,15 +126,6 @@ public class Email extends Application {
|
|||
|
||||
private static void setServicesEnabled(Context context, boolean enabled) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
if (!enabled && pm.getComponentEnabledSetting(
|
||||
new ComponentName(context, MailService.class)) ==
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
|
||||
/*
|
||||
* If no accounts now exist but the service is still enabled we're about to disable it
|
||||
* so we'll reschedule to kill off any existing alarms.
|
||||
*/
|
||||
MailService.actionReschedule(context);
|
||||
}
|
||||
pm.setComponentEnabledSetting(
|
||||
new ComponentName(context, MessageCompose.class),
|
||||
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
|
||||
|
@ -156,15 +146,6 @@ public class Email extends Application {
|
|||
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
if (enabled && pm.getComponentEnabledSetting(
|
||||
new ComponentName(context, MailService.class)) ==
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
|
||||
/*
|
||||
* And now if accounts do exist then we've just enabled the service and we want to
|
||||
* schedule alarms for the new accounts.
|
||||
*/
|
||||
MailService.actionReschedule(context);
|
||||
}
|
||||
|
||||
// Start/stop the various services depending on whether there are any accounts
|
||||
startOrStopService(enabled, context, new Intent(context, AttachmentDownloadService.class));
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,7 +21,6 @@ import com.android.email.Preferences;
|
|||
import com.android.email.R;
|
||||
import com.android.email.activity.UiUtilities;
|
||||
import com.android.email.service.EmailServiceUtils;
|
||||
import com.android.email.service.MailService;
|
||||
import com.android.emailcommon.Logging;
|
||||
|
||||
import android.app.Fragment;
|
||||
|
@ -44,7 +43,6 @@ public class DebugFragment extends Fragment implements OnCheckedChangeListener,
|
|||
private CheckBox mEnableExchangeLoggingView;
|
||||
private CheckBox mEnableExchangeFileLoggingView;
|
||||
private CheckBox mInhibitGraphicsAccelerationView;
|
||||
private CheckBox mForceOneMinuteRefreshView;
|
||||
private CheckBox mEnableStrictModeView;
|
||||
|
||||
private Preferences mPreferences;
|
||||
|
@ -92,11 +90,6 @@ public class DebugFragment extends Fragment implements OnCheckedChangeListener,
|
|||
mInhibitGraphicsAccelerationView.setChecked(Email.sDebugInhibitGraphicsAcceleration);
|
||||
mInhibitGraphicsAccelerationView.setOnCheckedChangeListener(this);
|
||||
|
||||
mForceOneMinuteRefreshView = (CheckBox)
|
||||
UiUtilities.getView(view, R.id.debug_force_one_minute_refresh);
|
||||
mForceOneMinuteRefreshView.setChecked(mPreferences.getForceOneMinuteRefresh());
|
||||
mForceOneMinuteRefreshView.setOnCheckedChangeListener(this);
|
||||
|
||||
mEnableStrictModeView = (CheckBox)
|
||||
UiUtilities.getView(view, R.id.debug_enable_strict_mode);
|
||||
mEnableStrictModeView.setChecked(mPreferences.getEnableStrictMode());
|
||||
|
@ -125,10 +118,6 @@ public class DebugFragment extends Fragment implements OnCheckedChangeListener,
|
|||
Email.sDebugInhibitGraphicsAcceleration = isChecked;
|
||||
mPreferences.setInhibitGraphicsAcceleration(isChecked);
|
||||
break;
|
||||
case R.id.debug_force_one_minute_refresh:
|
||||
mPreferences.setForceOneMinuteRefresh(isChecked);
|
||||
MailService.actionReschedule(getActivity());
|
||||
break;
|
||||
case R.id.debug_enable_strict_mode:
|
||||
mPreferences.setEnableStrictMode(isChecked);
|
||||
Email.enableStrictMode(isChecked);
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email.provider;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.LegacyConversions;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.internet.MimeUtility;
|
||||
import com.android.emailcommon.mail.Message;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.mail.Part;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.utility.ConversionUtilities;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Utilities {
|
||||
/**
|
||||
* Copy one downloaded message (which may have partially-loaded sections)
|
||||
* into a newly created EmailProvider Message, given the account and mailbox
|
||||
*
|
||||
* @param message the remote message we've just downloaded
|
||||
* @param account the account it will be stored into
|
||||
* @param folder the mailbox it will be stored into
|
||||
* @param loadStatus when complete, the message will be marked with this status (e.g.
|
||||
* EmailContent.Message.LOADED)
|
||||
*/
|
||||
public static void copyOneMessageToProvider(Context context, Message message, Account account,
|
||||
Mailbox folder, int loadStatus) {
|
||||
EmailContent.Message localMessage = null;
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = context.getContentResolver().query(
|
||||
EmailContent.Message.CONTENT_URI,
|
||||
EmailContent.Message.CONTENT_PROJECTION,
|
||||
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
|
||||
" AND " + MessageColumns.MAILBOX_KEY + "=?" +
|
||||
" AND " + SyncColumns.SERVER_ID + "=?",
|
||||
new String[] {
|
||||
String.valueOf(account.mId),
|
||||
String.valueOf(folder.mId),
|
||||
String.valueOf(message.getUid())
|
||||
},
|
||||
null);
|
||||
if (c.moveToNext()) {
|
||||
localMessage = EmailContent.getContent(c, EmailContent.Message.class);
|
||||
localMessage.mMailboxKey = folder.mId;
|
||||
localMessage.mAccountKey = account.mId;
|
||||
copyOneMessageToProvider(context, message, localMessage, loadStatus);
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy one downloaded message (which may have partially-loaded sections)
|
||||
* into an already-created EmailProvider Message
|
||||
*
|
||||
* @param message the remote message we've just downloaded
|
||||
* @param localMessage the EmailProvider Message, already created
|
||||
* @param loadStatus when complete, the message will be marked with this status (e.g.
|
||||
* EmailContent.Message.LOADED)
|
||||
* @param context the context to be used for EmailProvider
|
||||
*/
|
||||
public static void copyOneMessageToProvider(Context context, Message message,
|
||||
EmailContent.Message localMessage, int loadStatus) {
|
||||
try {
|
||||
|
||||
EmailContent.Body body = EmailContent.Body.restoreBodyWithMessageId(context,
|
||||
localMessage.mId);
|
||||
if (body == null) {
|
||||
body = new EmailContent.Body();
|
||||
}
|
||||
try {
|
||||
// Copy the fields that are available into the message object
|
||||
LegacyConversions.updateMessageFields(localMessage, message,
|
||||
localMessage.mAccountKey, localMessage.mMailboxKey);
|
||||
|
||||
// Now process body parts & attachments
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
|
||||
ConversionUtilities.updateBodyFields(body, localMessage, viewables);
|
||||
|
||||
// Commit the message & body to the local store immediately
|
||||
saveOrUpdate(localMessage, context);
|
||||
saveOrUpdate(body, context);
|
||||
|
||||
// process (and save) attachments
|
||||
LegacyConversions.updateAttachments(context, localMessage, attachments);
|
||||
|
||||
// One last update of message with two updated flags
|
||||
localMessage.mFlagLoaded = loadStatus;
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(EmailContent.MessageColumns.FLAG_ATTACHMENT, localMessage.mFlagAttachment);
|
||||
cv.put(EmailContent.MessageColumns.FLAG_LOADED, localMessage.mFlagLoaded);
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI,
|
||||
localMessage.mId);
|
||||
context.getContentResolver().update(uri, cv, null, null);
|
||||
|
||||
} catch (MessagingException me) {
|
||||
Log.e(Logging.LOG_TAG, "Error while copying downloaded message." + me);
|
||||
}
|
||||
|
||||
} catch (RuntimeException rte) {
|
||||
Log.e(Logging.LOG_TAG, "Error while storing downloaded message." + rte.toString());
|
||||
} catch (IOException ioe) {
|
||||
Log.e(Logging.LOG_TAG, "Error while storing attachment." + ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveOrUpdate(EmailContent content, Context context) {
|
||||
if (content.isSaved()) {
|
||||
content.update(context, content.toContentValues());
|
||||
} else {
|
||||
content.save(context);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -33,7 +33,6 @@ import android.text.format.DateUtils;
|
|||
import android.util.Log;
|
||||
|
||||
import com.android.email.AttachmentInfo;
|
||||
import com.android.email.Controller.ControllerService;
|
||||
import com.android.email.Email;
|
||||
import com.android.email.EmailConnectivityManager;
|
||||
import com.android.email.NotificationController;
|
||||
|
@ -450,8 +449,8 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
|||
* @return whether or not the download was started
|
||||
*/
|
||||
/*package*/ synchronized boolean tryStartDownload(DownloadRequest req) {
|
||||
Intent intent = getServiceIntentForAccount(req.accountId);
|
||||
if (intent == null) return false;
|
||||
EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(
|
||||
AttachmentDownloadService.this, mServiceCallback, req.accountId);
|
||||
|
||||
// Do not download the same attachment multiple times
|
||||
boolean alreadyInProgress = mDownloadsInProgress.get(req.attachmentId) != null;
|
||||
|
@ -461,7 +460,7 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
|||
if (Email.DEBUG) {
|
||||
Log.d(TAG, ">> Starting download for attachment #" + req.attachmentId);
|
||||
}
|
||||
startDownload(intent, req);
|
||||
startDownload(service, req);
|
||||
} catch (RemoteException e) {
|
||||
// TODO: Consider whether we need to do more in this case...
|
||||
// For now, fix up our data to reflect the failure
|
||||
|
@ -478,18 +477,16 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
|||
* Do the work of starting an attachment download using the EmailService interface, and
|
||||
* set our watchdog alarm
|
||||
*
|
||||
* @param serviceClass the class that will attempt the download
|
||||
* @param serviceClass the service handling the download
|
||||
* @param req the DownloadRequest
|
||||
* @throws RemoteException
|
||||
*/
|
||||
private void startDownload(Intent intent, DownloadRequest req)
|
||||
private void startDownload(EmailServiceProxy service, DownloadRequest req)
|
||||
throws RemoteException {
|
||||
req.startTime = System.currentTimeMillis();
|
||||
req.inProgress = true;
|
||||
mDownloadsInProgress.put(req.attachmentId, req);
|
||||
EmailServiceProxy proxy =
|
||||
new EmailServiceProxy(mContext, intent, mServiceCallback);
|
||||
proxy.loadAttachment(req.attachmentId, req.priority != PRIORITY_FOREGROUND);
|
||||
service.loadAttachment(req.attachmentId, req.priority != PRIORITY_FOREGROUND);
|
||||
// Lazily initialize our (reusable) pending intent
|
||||
if (mWatchdogPendingIntent == null) {
|
||||
createWatchdogPendingIntent(mContext);
|
||||
|
@ -685,11 +682,6 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageStatus(long messageId, String subject, int statusCode, int progress)
|
||||
throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxListStatus(long accountId, int statusCode, int progress)
|
||||
throws RemoteException {
|
||||
|
@ -700,33 +692,17 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
|||
throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageStatus(long messageId, String subject, int statusCode, int progress)
|
||||
throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessageStatus(long messageId, int statusCode, int progress)
|
||||
throws RemoteException {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an Intent to be used used based on the account type of the provided account id. We
|
||||
* cache the results to avoid repeated database access
|
||||
* @param accountId the id of the account
|
||||
* @return the Intent to be used for the account or null (if the account no longer exists)
|
||||
*/
|
||||
private synchronized Intent getServiceIntentForAccount(long accountId) {
|
||||
// TODO: We should have some more data-driven way of determining the service intent.
|
||||
Intent serviceIntent = mAccountServiceMap.get(accountId);
|
||||
if (serviceIntent == null) {
|
||||
String protocol = Account.getProtocol(mContext, accountId);
|
||||
if (protocol == null) return null;
|
||||
serviceIntent = new Intent(mContext, ControllerService.class);
|
||||
if (protocol.equals("eas")) {
|
||||
serviceIntent = new Intent(EmailServiceProxy.EXCHANGE_INTENT);
|
||||
}
|
||||
mAccountServiceMap.put(accountId, serviceIntent);
|
||||
}
|
||||
return serviceIntent;
|
||||
}
|
||||
|
||||
/*package*/ void addServiceIntentForTest(long accountId, Intent intent) {
|
||||
mAccountServiceMap.put(accountId, intent);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ import android.database.Cursor;
|
|||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.Email;
|
||||
import com.android.email.Preferences;
|
||||
import com.android.email.SecurityPolicy;
|
||||
import com.android.email.VendorPolicyLoader;
|
||||
|
@ -106,15 +105,7 @@ public class EmailBroadcastProcessorService extends IntentService {
|
|||
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(broadcastAction)) {
|
||||
onBootCompleted();
|
||||
|
||||
// TODO: Do a better job when we get ACTION_DEVICE_STORAGE_LOW.
|
||||
// The code below came from very old code....
|
||||
} else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(broadcastAction)) {
|
||||
// Stop IMAP/POP3 poll.
|
||||
MailService.actionCancel(this);
|
||||
} else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(broadcastAction)) {
|
||||
enableComponentsIfNecessary();
|
||||
} else if (ACTION_SECRET_CODE.equals(broadcastAction)
|
||||
} else if (ACTION_SECRET_CODE.equals(broadcastAction)
|
||||
&& SECRET_CODE_HOST_DEBUG_SCREEN.equals(broadcastIntent.getData().getHost())) {
|
||||
AccountSettings.actionSettingsWithDebug(this);
|
||||
} else if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(broadcastAction)) {
|
||||
|
@ -126,22 +117,12 @@ public class EmailBroadcastProcessorService extends IntentService {
|
|||
}
|
||||
}
|
||||
|
||||
private void enableComponentsIfNecessary() {
|
||||
if (Email.setServicesEnabledSync(this)) {
|
||||
// At least one account exists.
|
||||
// TODO probably we should check if it's a POP/IMAP account.
|
||||
MailService.actionReschedule(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles {@link Intent#ACTION_BOOT_COMPLETED}. Called on a worker thread.
|
||||
*/
|
||||
private void onBootCompleted() {
|
||||
performOneTimeInitialization();
|
||||
|
||||
enableComponentsIfNecessary();
|
||||
|
||||
// Starts the service for Exchange, if supported.
|
||||
EmailServiceUtils.startExchangeService(this);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,517 @@
|
|||
/* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email.service;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.Email;
|
||||
import com.android.email.LegacyConversions;
|
||||
import com.android.email.NotificationController;
|
||||
import com.android.email.Controller.Result;
|
||||
import com.android.email.mail.Sender;
|
||||
import com.android.email.mail.Store;
|
||||
import com.android.email.provider.Utilities;
|
||||
import com.android.emailcommon.AccountManagerTypes;
|
||||
import com.android.emailcommon.Api;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.TrafficFlags;
|
||||
import com.android.emailcommon.internet.MimeBodyPart;
|
||||
import com.android.emailcommon.internet.MimeHeader;
|
||||
import com.android.emailcommon.internet.MimeMultipart;
|
||||
import com.android.emailcommon.mail.AuthenticationFailedException;
|
||||
import com.android.emailcommon.mail.FetchProfile;
|
||||
import com.android.emailcommon.mail.Folder;
|
||||
import com.android.emailcommon.mail.Message;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.mail.Folder.MessageRetrievalListener;
|
||||
import com.android.emailcommon.mail.Folder.OpenMode;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.provider.EmailContent.Attachment;
|
||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.service.EmailServiceStatus;
|
||||
import com.android.emailcommon.service.IEmailService;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||
import com.android.emailcommon.service.SearchParams;
|
||||
import com.android.emailcommon.utility.AttachmentUtilities;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* EmailServiceStub is an abstract class representing an EmailService
|
||||
*
|
||||
* This class provides legacy support for a few methods that are common to both
|
||||
* IMAP and POP3, including startSync, loadMore, loadAttachment, and sendMail
|
||||
*/
|
||||
public abstract class EmailServiceStub extends IEmailService.Stub implements IEmailService {
|
||||
|
||||
private static final int MAILBOX_COLUMN_ID = 0;
|
||||
private static final int MAILBOX_COLUMN_SERVER_ID = 1;
|
||||
private static final int MAILBOX_COLUMN_TYPE = 2;
|
||||
|
||||
/** Small projection for just the columns required for a sync. */
|
||||
private static final String[] MAILBOX_PROJECTION = new String[] {
|
||||
MailboxColumns.ID,
|
||||
MailboxColumns.SERVER_ID,
|
||||
MailboxColumns.TYPE,
|
||||
};
|
||||
|
||||
private Context mContext;
|
||||
private IEmailServiceCallback.Stub mCallback;
|
||||
|
||||
protected void init(Context context, IEmailServiceCallback.Stub callbackProxy) {
|
||||
mContext = context;
|
||||
mCallback = callbackProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle validate(HostAuth hostauth) throws RemoteException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSync(long mailboxId, boolean userRequest) throws RemoteException {
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
||||
if (mailbox == null) return;
|
||||
Account account = Account.restoreAccountWithId(mContext, mailbox.mAccountKey);
|
||||
if (account == null) return;
|
||||
android.accounts.Account acct = new android.accounts.Account(account.mEmailAddress,
|
||||
AccountManagerTypes.TYPE_POP_IMAP);
|
||||
Bundle extras = new Bundle();
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
||||
ContentResolver.requestSync(acct, EmailContent.AUTHORITY, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSync(long mailboxId) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMore(long messageId) throws RemoteException {
|
||||
// Load a message for view...
|
||||
try {
|
||||
// 1. Resample the message, in case it disappeared or synced while
|
||||
// this command was in queue
|
||||
EmailContent.Message message =
|
||||
EmailContent.Message.restoreMessageWithId(mContext, messageId);
|
||||
if (message == null) {
|
||||
mCallback.loadMessageStatus(messageId,
|
||||
EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
|
||||
return;
|
||||
}
|
||||
if (message.mFlagLoaded == EmailContent.Message.FLAG_LOADED_COMPLETE) {
|
||||
// We should NEVER get here
|
||||
mCallback.loadMessageStatus(messageId, 0, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Open the remote folder.
|
||||
// TODO combine with common code in loadAttachment
|
||||
Account account = Account.restoreAccountWithId(mContext, message.mAccountKey);
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
|
||||
if (account == null || mailbox == null) {
|
||||
//mListeners.loadMessageForViewFailed(messageId, "null account or mailbox");
|
||||
return;
|
||||
}
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account));
|
||||
|
||||
Store remoteStore = Store.getInstance(account, mContext);
|
||||
String remoteServerId = mailbox.mServerId;
|
||||
// If this is a search result, use the protocolSearchInfo field to get the
|
||||
// correct remote location
|
||||
if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
|
||||
remoteServerId = message.mProtocolSearchInfo;
|
||||
}
|
||||
Folder remoteFolder = remoteStore.getFolder(remoteServerId);
|
||||
remoteFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
// 3. Set up to download the entire message
|
||||
Message remoteMessage = remoteFolder.getMessage(message.mServerId);
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
remoteFolder.fetch(new Message[] { remoteMessage }, fp, null);
|
||||
|
||||
// 4. Write to provider
|
||||
Utilities.copyOneMessageToProvider(mContext, remoteMessage, account, mailbox,
|
||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||
|
||||
// 5. Notify UI
|
||||
mCallback.loadMessageStatus(messageId, 0, 100);
|
||||
|
||||
} catch (MessagingException me) {
|
||||
if (Logging.LOGD) Log.v(Logging.LOG_TAG, "", me);
|
||||
mCallback.loadMessageStatus(messageId, EmailServiceStatus.REMOTE_EXCEPTION, 0);
|
||||
} catch (RuntimeException rte) {
|
||||
mCallback.loadMessageStatus(messageId, EmailServiceStatus.REMOTE_EXCEPTION, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachment(long attachmentId, boolean background) throws RemoteException {
|
||||
try {
|
||||
//1. Check if the attachment is already here and return early in that case
|
||||
Attachment attachment =
|
||||
Attachment.restoreAttachmentWithId(mContext, attachmentId);
|
||||
if (attachment == null) {
|
||||
// mListeners.loadAttachmentFailed(accountId, messageId, attachmentId,
|
||||
// new MessagingException("The attachment is null"),
|
||||
// background);
|
||||
return;
|
||||
}
|
||||
if (Utility.attachmentExists(mContext, attachment)) {
|
||||
// mListeners.loadAttachmentFinished(accountId, messageId, attachmentId);
|
||||
return;
|
||||
}
|
||||
EmailContent.Message message =
|
||||
EmailContent.Message.restoreMessageWithId(mContext, attachment.mMessageKey);
|
||||
|
||||
// 2. Open the remote folder.
|
||||
// TODO all of these could be narrower projections
|
||||
Account account = Account.restoreAccountWithId(mContext, message.mAccountKey);
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
|
||||
|
||||
if (account == null || mailbox == null || message == null) {
|
||||
// mListeners.loadAttachmentFailed(accountId, messageId, attachmentId,
|
||||
// new MessagingException(
|
||||
// "Account, mailbox, message or attachment are null"),
|
||||
// background);
|
||||
return;
|
||||
}
|
||||
TrafficStats.setThreadStatsTag(
|
||||
TrafficFlags.getAttachmentFlags(mContext, account));
|
||||
|
||||
Store remoteStore = Store.getInstance(account, mContext);
|
||||
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
||||
remoteFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
// 3. Generate a shell message in which to retrieve the attachment,
|
||||
// and a shell BodyPart for the attachment. Then glue them together.
|
||||
Message storeMessage = remoteFolder.createMessage(message.mServerId);
|
||||
MimeBodyPart storePart = new MimeBodyPart();
|
||||
storePart.setSize((int)attachment.mSize);
|
||||
storePart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA,
|
||||
attachment.mLocation);
|
||||
storePart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
||||
String.format("%s;\n name=\"%s\"",
|
||||
attachment.mMimeType,
|
||||
attachment.mFileName));
|
||||
// TODO is this always true for attachments? I think we dropped the
|
||||
// true encoding along the way
|
||||
storePart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
|
||||
MimeMultipart multipart = new MimeMultipart();
|
||||
multipart.setSubType("mixed");
|
||||
multipart.addBodyPart(storePart);
|
||||
|
||||
storeMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed");
|
||||
storeMessage.setBody(multipart);
|
||||
|
||||
// 4. Now ask for the attachment to be fetched
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(storePart);
|
||||
remoteFolder.fetch(new Message[] { storeMessage }, fp,
|
||||
new MessageRetrievalListenerBridge(message.mId, attachmentId));
|
||||
|
||||
// If we failed to load the attachment, throw an Exception here, so that
|
||||
// AttachmentDownloadService knows that we failed
|
||||
if (storePart.getBody() == null) {
|
||||
throw new MessagingException("Attachment not loaded.");
|
||||
}
|
||||
|
||||
// 5. Save the downloaded file and update the attachment as necessary
|
||||
LegacyConversions.saveAttachmentBody(mContext, storePart, attachment,
|
||||
message.mAccountKey);
|
||||
|
||||
// 6. Report success
|
||||
// mListeners.loadAttachmentFinished(accountId, messageId, attachmentId);
|
||||
}
|
||||
catch (MessagingException me) {
|
||||
if (Logging.LOGD) Log.v(Logging.LOG_TAG, "", me);
|
||||
// mListeners.loadAttachmentFailed(
|
||||
// accountId, messageId, attachmentId, me, background);
|
||||
} catch (IOException ioe) {
|
||||
Log.e(Logging.LOG_TAG, "Error while storing attachment." + ioe.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: Implement callback
|
||||
@Override
|
||||
public void updateFolderList(long accountId) throws RemoteException {
|
||||
Account account = Account.restoreAccountWithId(mContext, accountId);
|
||||
if (account == null) return;
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account));
|
||||
Cursor localFolderCursor = null;
|
||||
try {
|
||||
// Step 1: Get remote mailboxes
|
||||
Store store = Store.getInstance(account, mContext);
|
||||
Folder[] remoteFolders = store.updateFolders();
|
||||
HashSet<String> remoteFolderNames = new HashSet<String>();
|
||||
for (int i = 0, count = remoteFolders.length; i < count; i++) {
|
||||
remoteFolderNames.add(remoteFolders[i].getName());
|
||||
}
|
||||
|
||||
// Step 2: Get local mailboxes
|
||||
localFolderCursor = mContext.getContentResolver().query(
|
||||
Mailbox.CONTENT_URI,
|
||||
MAILBOX_PROJECTION,
|
||||
EmailContent.MailboxColumns.ACCOUNT_KEY + "=?",
|
||||
new String[] { String.valueOf(account.mId) },
|
||||
null);
|
||||
|
||||
// Step 3: Remove any local mailbox not on the remote list
|
||||
while (localFolderCursor.moveToNext()) {
|
||||
String mailboxPath = localFolderCursor.getString(MAILBOX_COLUMN_SERVER_ID);
|
||||
// Short circuit if we have a remote mailbox with the same name
|
||||
if (remoteFolderNames.contains(mailboxPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int mailboxType = localFolderCursor.getInt(MAILBOX_COLUMN_TYPE);
|
||||
long mailboxId = localFolderCursor.getLong(MAILBOX_COLUMN_ID);
|
||||
switch (mailboxType) {
|
||||
case Mailbox.TYPE_INBOX:
|
||||
case Mailbox.TYPE_DRAFTS:
|
||||
case Mailbox.TYPE_OUTBOX:
|
||||
case Mailbox.TYPE_SENT:
|
||||
case Mailbox.TYPE_TRASH:
|
||||
case Mailbox.TYPE_SEARCH:
|
||||
// Never, ever delete special mailboxes
|
||||
break;
|
||||
default:
|
||||
// Drop all attachment files related to this mailbox
|
||||
AttachmentUtilities.deleteAllMailboxAttachmentFiles(
|
||||
mContext, accountId, mailboxId);
|
||||
// Delete the mailbox; database triggers take care of related
|
||||
// Message, Body and Attachment records
|
||||
Uri uri = ContentUris.withAppendedId(
|
||||
Mailbox.CONTENT_URI, mailboxId);
|
||||
mContext.getContentResolver().delete(uri, null, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//mListeners.listFoldersFinished(accountId);
|
||||
} catch (Exception e) {
|
||||
//mListeners.listFoldersFailed(accountId, e.toString());
|
||||
} finally {
|
||||
if (localFolderCursor != null) {
|
||||
localFolderCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFolder(long accountId, String name) throws RemoteException {
|
||||
// Not required
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteFolder(long accountId, String name) throws RemoteException {
|
||||
// Not required
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameFolder(long accountId, String oldName, String newName)
|
||||
throws RemoteException {
|
||||
// Not required
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(IEmailServiceCallback cb) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogging(int on) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hostChanged(long accountId) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle autoDiscover(String userName, String password) throws RemoteException {
|
||||
// Not required
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMeetingResponse(long messageId, int response) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccountPIMData(long accountId) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getApiLevel() throws RemoteException {
|
||||
return Api.LEVEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int searchMessages(long accountId, SearchParams params, long destMailboxId)
|
||||
throws RemoteException {
|
||||
// Not required
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMail(long accountId) throws RemoteException {
|
||||
Account account = Account.restoreAccountWithId(mContext, accountId);
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSmtpFlags(mContext, account));
|
||||
NotificationController nc = NotificationController.getInstance(mContext);
|
||||
// 1. Loop through all messages in the account's outbox
|
||||
long outboxId = Mailbox.findMailboxOfType(mContext, account.mId, Mailbox.TYPE_OUTBOX);
|
||||
if (outboxId == Mailbox.NO_MAILBOX) {
|
||||
return;
|
||||
}
|
||||
ContentResolver resolver = mContext.getContentResolver();
|
||||
Cursor c = resolver.query(EmailContent.Message.CONTENT_URI,
|
||||
EmailContent.Message.ID_COLUMN_PROJECTION,
|
||||
EmailContent.Message.MAILBOX_KEY + "=?", new String[] { Long.toString(outboxId) },
|
||||
null);
|
||||
try {
|
||||
// 2. exit early
|
||||
if (c.getCount() <= 0) {
|
||||
return;
|
||||
}
|
||||
// 3. do one-time setup of the Sender & other stuff
|
||||
//mListeners.sendPendingMessagesStarted(account.mId, -1);
|
||||
|
||||
Sender sender = Sender.getInstance(mContext, account);
|
||||
Store remoteStore = Store.getInstance(account, mContext);
|
||||
boolean requireMoveMessageToSentFolder = remoteStore.requireCopyMessageToSentFolder();
|
||||
ContentValues moveToSentValues = null;
|
||||
if (requireMoveMessageToSentFolder) {
|
||||
Mailbox sentFolder =
|
||||
Mailbox.restoreMailboxOfType(mContext, accountId, Mailbox.TYPE_SENT);
|
||||
moveToSentValues = new ContentValues();
|
||||
moveToSentValues.put(MessageColumns.MAILBOX_KEY, sentFolder.mId);
|
||||
}
|
||||
|
||||
// 4. loop through the available messages and send them
|
||||
while (c.moveToNext()) {
|
||||
long messageId = -1;
|
||||
try {
|
||||
messageId = c.getLong(0);
|
||||
//mListeners.sendPendingMessagesStarted(account.mId, messageId);
|
||||
// Don't send messages with unloaded attachments
|
||||
if (Utility.hasUnloadedAttachments(mContext, messageId)) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Logging.LOG_TAG, "Can't send #" + messageId +
|
||||
"; unloaded attachments");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
sender.sendMessage(messageId);
|
||||
} catch (MessagingException me) {
|
||||
// report error for this message, but keep trying others
|
||||
if (me instanceof AuthenticationFailedException) {
|
||||
nc.showLoginFailedNotification(account.mId);
|
||||
}
|
||||
//mListeners.sendPendingMessagesFailed(account.mId, messageId, me);
|
||||
continue;
|
||||
}
|
||||
// 5. move to sent, or delete
|
||||
Uri syncedUri =
|
||||
ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId);
|
||||
if (requireMoveMessageToSentFolder) {
|
||||
// If this is a forwarded message and it has attachments, delete them, as they
|
||||
// duplicate information found elsewhere (on the server). This saves storage.
|
||||
EmailContent.Message msg =
|
||||
EmailContent.Message.restoreMessageWithId(mContext, messageId);
|
||||
if (msg != null &&
|
||||
((msg.mFlags & EmailContent.Message.FLAG_TYPE_FORWARD) != 0)) {
|
||||
AttachmentUtilities.deleteAllAttachmentFiles(mContext, account.mId,
|
||||
messageId);
|
||||
}
|
||||
resolver.update(syncedUri, moveToSentValues, null, null);
|
||||
} else {
|
||||
AttachmentUtilities.deleteAllAttachmentFiles(mContext, account.mId,
|
||||
messageId);
|
||||
Uri uri =
|
||||
ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId);
|
||||
resolver.delete(uri, null, null);
|
||||
resolver.delete(syncedUri, null, null);
|
||||
}
|
||||
}
|
||||
// 6. report completion/success
|
||||
//mListeners.sendPendingMessagesCompleted(account.mId);
|
||||
nc.cancelLoginFailedNotification(account.mId);
|
||||
} catch (MessagingException me) {
|
||||
if (me instanceof AuthenticationFailedException) {
|
||||
nc.showLoginFailedNotification(account.mId);
|
||||
}
|
||||
//mListeners.sendPendingMessagesFailed(account.mId, -1, me);
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bridge to intercept {@link MessageRetrievalListener#loadAttachmentProgress} and
|
||||
* pass down to {@link Result}.
|
||||
*/
|
||||
public class MessageRetrievalListenerBridge implements MessageRetrievalListener {
|
||||
private final long mMessageId;
|
||||
// private final long mAttachmentId;
|
||||
// private final long mAccountId;
|
||||
|
||||
public MessageRetrievalListenerBridge(long messageId, long attachmentId) {
|
||||
mMessageId = messageId;
|
||||
// mAttachmentId = attachmentId;
|
||||
// mAccountId = Account.getAccountIdForMessageId(mContext, mMessageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentProgress(int progress) {
|
||||
// synchronized (mListeners) {
|
||||
// for (Result listener : mListeners) {
|
||||
// listener.loadAttachmentCallback(null, mAccountId, mMessageId, mAttachmentId,
|
||||
// progress);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageRetrieved(com.android.emailcommon.mail.Message message) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,19 +16,13 @@
|
|||
|
||||
package com.android.email.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.emailcommon.Api;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.service.EmailServiceProxy;
|
||||
import com.android.emailcommon.service.IEmailService;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||
import com.android.emailcommon.service.SearchParams;
|
||||
|
||||
/**
|
||||
* Utility functions for EmailService support.
|
||||
|
@ -74,106 +68,32 @@ public class EmailServiceUtils {
|
|||
return new EmailServiceProxy(context, ImapService.class, callback);
|
||||
}
|
||||
|
||||
public static EmailServiceProxy getPop3Service(Context context,
|
||||
IEmailServiceCallback callback) {
|
||||
return new EmailServiceProxy(context, Pop3Service.class, callback);
|
||||
}
|
||||
|
||||
public static boolean isExchangeAvailable(Context context) {
|
||||
return isServiceAvailable(context, EmailServiceProxy.EXCHANGE_INTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* An empty {@link IEmailService} implementation which is used instead of
|
||||
* {@link com.android.exchange.ExchangeService} on the build with no exchange support.
|
||||
* For a given account id, return a service proxy if applicable, or null.
|
||||
*
|
||||
* <p>In theory, the service in question isn't used on the no-exchange-support build,
|
||||
* because we won't have any exchange accounts in that case, so we wouldn't have to have this
|
||||
* class. However, there are a few places we do use the service even if there's no exchange
|
||||
* accounts (e.g. setLogging), so this class is added for safety and simplicity.
|
||||
* @param accountId the message of interest
|
||||
* @result service proxy, or null if n/a
|
||||
*/
|
||||
public static class NullEmailService extends Service implements IEmailService {
|
||||
public static final NullEmailService INSTANCE = new NullEmailService();
|
||||
|
||||
@Override
|
||||
public int getApiLevel() {
|
||||
return Api.LEVEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle autoDiscover(String userName, String password) throws RemoteException {
|
||||
return Bundle.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFolder(long accountId, String name) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteFolder(long accountId, String name) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hostChanged(long accountId) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachment(long attachmentId, boolean background) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMore(long messageId) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameFolder(long accountId, String oldName, String newName)
|
||||
throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMeetingResponse(long messageId, int response) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(IEmailServiceCallback cb) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogging(int flags) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSync(long mailboxId, boolean userRequest) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSync(long mailboxId) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFolderList(long accountId) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle validate(HostAuth hostAuth) throws RemoteException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccountPIMData(long accountId) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int searchMessages(long accountId, SearchParams searchParams, long destMailboxId) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
public static EmailServiceProxy getServiceForAccount(Context context,
|
||||
IEmailServiceCallback callback, long accountId) {
|
||||
String protocol = Account.getProtocol(context, accountId);
|
||||
if (protocol.equals(HostAuth.SCHEME_IMAP)) {
|
||||
return getImapService(context, callback);
|
||||
} else if (protocol.equals(HostAuth.SCHEME_POP3)) {
|
||||
return getPop3Service(context, callback);
|
||||
} else if (protocol.equals(HostAuth.SCHEME_EAS)) {
|
||||
return getExchangeService(context, callback);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Account with unknown protocol: " + accountId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import android.content.Intent;
|
|||
import android.database.Cursor;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
|
@ -36,8 +35,7 @@ import com.android.email.Email;
|
|||
import com.android.email.LegacyConversions;
|
||||
import com.android.email.NotificationController;
|
||||
import com.android.email.mail.Store;
|
||||
import com.android.emailcommon.AccountManagerTypes;
|
||||
import com.android.emailcommon.Api;
|
||||
import com.android.email.provider.Utilities;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.TrafficFlags;
|
||||
import com.android.emailcommon.internet.MimeUtility;
|
||||
|
@ -57,16 +55,10 @@ import com.android.emailcommon.provider.EmailContent;
|
|||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.service.EmailServiceStatus;
|
||||
import com.android.emailcommon.service.IEmailService;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||
import com.android.emailcommon.service.SearchParams;
|
||||
import com.android.emailcommon.utility.AttachmentUtilities;
|
||||
import com.android.emailcommon.utility.ConversionUtilities;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
@ -81,6 +73,14 @@ public class ImapService extends Service {
|
|||
private static final Flag[] FLAG_LIST_FLAGGED = new Flag[] { Flag.FLAGGED };
|
||||
private static final Flag[] FLAG_LIST_ANSWERED = new Flag[] { Flag.ANSWERED };
|
||||
|
||||
/**
|
||||
* Simple cache for last search result mailbox by account and serverId, since the most common
|
||||
* case will be repeated use of the same mailbox
|
||||
*/
|
||||
private static long mLastSearchAccountKey = Account.NO_ACCOUNT;
|
||||
private static String mLastSearchServerId = null;
|
||||
private static Mailbox mLastSearchRemoteMailbox = null;
|
||||
|
||||
/**
|
||||
* Cache search results by account; this allows for "load more" support without having to
|
||||
* redo the search (which can be quite slow). SortableMessage is a smallish class, so memory
|
||||
|
@ -203,164 +203,27 @@ public class ImapService extends Service {
|
|||
/**
|
||||
* Create our EmailService implementation here.
|
||||
*/
|
||||
private final IEmailService.Stub mBinder = new IEmailService.Stub() {
|
||||
|
||||
@Override
|
||||
public int getApiLevel() {
|
||||
return Api.LEVEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle validate(HostAuth hostAuth) throws RemoteException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle autoDiscover(String userName, String password) throws RemoteException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSync(long mailboxId, boolean userRequest) throws RemoteException {
|
||||
Context context = getApplicationContext();
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
|
||||
if (mailbox == null) return;
|
||||
Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
|
||||
if (account == null) return;
|
||||
android.accounts.Account acct = new android.accounts.Account(account.mEmailAddress,
|
||||
AccountManagerTypes.TYPE_POP_IMAP);
|
||||
Log.d(TAG, "startSync API requesting sync");
|
||||
Bundle extras = new Bundle();
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
||||
ContentResolver.requestSync(acct, EmailContent.AUTHORITY, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSync(long mailboxId) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachment(long attachmentId, boolean background) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFolderList(long accountId) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hostChanged(long accountId) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogging(int flags) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMeetingResponse(long messageId, int response) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMore(long messageId) throws RemoteException {
|
||||
// Load a message for view...
|
||||
Context context = getApplicationContext();
|
||||
try {
|
||||
// 1. Resample the message, in case it disappeared or synced while
|
||||
// this command was in queue
|
||||
EmailContent.Message message =
|
||||
EmailContent.Message.restoreMessageWithId(context, messageId);
|
||||
if (message == null) {
|
||||
sCallbackProxy.loadMessageStatus(messageId,
|
||||
EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
|
||||
return;
|
||||
}
|
||||
if (message.mFlagLoaded == EmailContent.Message.FLAG_LOADED_COMPLETE) {
|
||||
// We should NEVER get here
|
||||
sCallbackProxy.loadMessageStatus(messageId, 0, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Open the remote folder.
|
||||
// TODO combine with common code in loadAttachment
|
||||
Account account = Account.restoreAccountWithId(context, message.mAccountKey);
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, message.mMailboxKey);
|
||||
if (account == null || mailbox == null) {
|
||||
//mListeners.loadMessageForViewFailed(messageId, "null account or mailbox");
|
||||
return;
|
||||
}
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account));
|
||||
|
||||
Store remoteStore = Store.getInstance(account, context);
|
||||
String remoteServerId = mailbox.mServerId;
|
||||
// If this is a search result, use the protocolSearchInfo field to get the
|
||||
// correct remote location
|
||||
if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
|
||||
remoteServerId = message.mProtocolSearchInfo;
|
||||
}
|
||||
Folder remoteFolder = remoteStore.getFolder(remoteServerId);
|
||||
remoteFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
// 3. Set up to download the entire message
|
||||
Message remoteMessage = remoteFolder.getMessage(message.mServerId);
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
remoteFolder.fetch(new Message[] { remoteMessage }, fp, null);
|
||||
|
||||
// 4. Write to provider
|
||||
copyOneMessageToProvider(context, remoteMessage, account, mailbox,
|
||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||
|
||||
// 5. Notify UI
|
||||
sCallbackProxy.loadMessageStatus(messageId, 0, 100);
|
||||
|
||||
} catch (MessagingException me) {
|
||||
if (Logging.LOGD) Log.v(Logging.LOG_TAG, "", me);
|
||||
sCallbackProxy.loadMessageStatus(messageId, EmailServiceStatus.REMOTE_EXCEPTION, 0);
|
||||
} catch (RuntimeException rte) {
|
||||
sCallbackProxy.loadMessageStatus(messageId, EmailServiceStatus.REMOTE_EXCEPTION, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// The following three methods are not implemented in this version
|
||||
@Override
|
||||
public boolean createFolder(long accountId, String name) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteFolder(long accountId, String name) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameFolder(long accountId, String oldName, String newName)
|
||||
throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
private final EmailServiceStub mBinder = new EmailServiceStub() {
|
||||
|
||||
@Override
|
||||
public void setCallback(IEmailServiceCallback cb) throws RemoteException {
|
||||
mCallbackList.register(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete PIM (calendar, contacts) data for the specified account
|
||||
*
|
||||
* @param accountId the account whose data should be deleted
|
||||
* @throws RemoteException
|
||||
*/
|
||||
@Override
|
||||
public void deleteAccountPIMData(long accountId) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int searchMessages(long accountId, SearchParams searchParams, long destMailboxId) {
|
||||
try {
|
||||
return searchMailboxImpl(getApplicationContext(), accountId, searchParams,
|
||||
destMailboxId);
|
||||
} catch (MessagingException e) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
mBinder.init(this, sCallbackProxy);
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
|
@ -380,11 +243,7 @@ public class ImapService extends Service {
|
|||
NotificationController nc = NotificationController.getInstance(context);
|
||||
try {
|
||||
processPendingActionsSynchronous(context, account);
|
||||
|
||||
// Select generic sync or store-specific sync
|
||||
SyncResults results = synchronizeMailboxGeneric(context, account, folder);
|
||||
// The account might have been deleted
|
||||
if (results == null) return;
|
||||
synchronizeMailboxGeneric(context, account, folder);
|
||||
// Clear authentication notification for this account
|
||||
nc.cancelLoginFailedNotification(account.mId);
|
||||
} catch (MessagingException e) {
|
||||
|
@ -436,14 +295,6 @@ public class ImapService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
private static void saveOrUpdate(EmailContent content, Context context) {
|
||||
if (content.isSaved()) {
|
||||
content.update(context, content.toContentValues());
|
||||
} else {
|
||||
content.save(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the structure and body of messages not yet synced
|
||||
* @param account the account we're syncing
|
||||
|
@ -488,7 +339,7 @@ public class ImapService extends Service {
|
|||
@Override
|
||||
public void messageRetrieved(Message message) {
|
||||
// Store the updated message locally and mark it fully loaded
|
||||
copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
Utilities.copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||
}
|
||||
|
||||
|
@ -515,7 +366,7 @@ public class ImapService extends Service {
|
|||
remoteFolder.fetch(new Message[] { message }, fp, null);
|
||||
|
||||
// Store the partially-loaded message and mark it partially loaded
|
||||
copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
Utilities.copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
EmailContent.Message.FLAG_LOADED_PARTIAL);
|
||||
} else {
|
||||
// We have a structure to deal with, from which
|
||||
|
@ -534,7 +385,7 @@ public class ImapService extends Service {
|
|||
remoteFolder.fetch(new Message[] { message }, fp, null);
|
||||
}
|
||||
// Store the updated message locally and mark it fully loaded
|
||||
copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
Utilities.copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||
}
|
||||
}
|
||||
|
@ -579,7 +430,7 @@ public class ImapService extends Service {
|
|||
LegacyConversions.updateMessageFields(localMessage,
|
||||
message, account.mId, mailbox.mId);
|
||||
// Commit the message to the local store
|
||||
saveOrUpdate(localMessage, context);
|
||||
Utilities.saveOrUpdate(localMessage, context);
|
||||
// Track the "new" ness of the downloaded message
|
||||
if (!message.isSet(Flag.SEEN) && unseenMessages != null) {
|
||||
unseenMessages.add(localMessage.mId);
|
||||
|
@ -614,7 +465,7 @@ public class ImapService extends Service {
|
|||
* @return results of the sync pass
|
||||
* @throws MessagingException
|
||||
*/
|
||||
private static SyncResults synchronizeMailboxGeneric(final Context context,
|
||||
private static void synchronizeMailboxGeneric(final Context context,
|
||||
final Account account, final Mailbox mailbox) throws MessagingException {
|
||||
|
||||
/*
|
||||
|
@ -630,8 +481,7 @@ public class ImapService extends Service {
|
|||
|
||||
// 0. We do not ever sync DRAFTS or OUTBOX (down or up)
|
||||
if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
||||
int totalMessages = EmailContent.count(context, mailbox.getUri(), null, null);
|
||||
return new SyncResults(totalMessages, unseenMessages);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Get the message list from the local store and create an index of the uids
|
||||
|
@ -664,7 +514,7 @@ public class ImapService extends Service {
|
|||
|
||||
Store remoteStore = Store.getInstance(account, context);
|
||||
// The account might have been deleted
|
||||
if (remoteStore == null) return null;
|
||||
if (remoteStore == null) return;
|
||||
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
||||
|
||||
/*
|
||||
|
@ -678,7 +528,7 @@ public class ImapService extends Service {
|
|||
|| mailbox.mType == Mailbox.TYPE_DRAFTS) {
|
||||
if (!remoteFolder.exists()) {
|
||||
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
|
||||
return new SyncResults(0, unseenMessages);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -829,107 +679,6 @@ public class ImapService extends Service {
|
|||
|
||||
// 14. Clean up and report results
|
||||
remoteFolder.close(false);
|
||||
|
||||
return new SyncResults(remoteMessageCount, unseenMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy one downloaded message (which may have partially-loaded sections)
|
||||
* into a newly created EmailProvider Message, given the account and mailbox
|
||||
*
|
||||
* @param message the remote message we've just downloaded
|
||||
* @param account the account it will be stored into
|
||||
* @param folder the mailbox it will be stored into
|
||||
* @param loadStatus when complete, the message will be marked with this status (e.g.
|
||||
* EmailContent.Message.LOADED)
|
||||
*/
|
||||
public static void copyOneMessageToProvider(Context context, Message message, Account account,
|
||||
Mailbox folder, int loadStatus) {
|
||||
EmailContent.Message localMessage = null;
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = context.getContentResolver().query(
|
||||
EmailContent.Message.CONTENT_URI,
|
||||
EmailContent.Message.CONTENT_PROJECTION,
|
||||
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
|
||||
" AND " + MessageColumns.MAILBOX_KEY + "=?" +
|
||||
" AND " + SyncColumns.SERVER_ID + "=?",
|
||||
new String[] {
|
||||
String.valueOf(account.mId),
|
||||
String.valueOf(folder.mId),
|
||||
String.valueOf(message.getUid())
|
||||
},
|
||||
null);
|
||||
if (c.moveToNext()) {
|
||||
localMessage = EmailContent.getContent(c, EmailContent.Message.class);
|
||||
localMessage.mMailboxKey = folder.mId;
|
||||
localMessage.mAccountKey = account.mId;
|
||||
copyOneMessageToProvider(context, message, localMessage, loadStatus);
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy one downloaded message (which may have partially-loaded sections)
|
||||
* into an already-created EmailProvider Message
|
||||
*
|
||||
* @param message the remote message we've just downloaded
|
||||
* @param localMessage the EmailProvider Message, already created
|
||||
* @param loadStatus when complete, the message will be marked with this status (e.g.
|
||||
* EmailContent.Message.LOADED)
|
||||
* @param context the context to be used for EmailProvider
|
||||
*/
|
||||
public static void copyOneMessageToProvider(Context context, Message message,
|
||||
EmailContent.Message localMessage, int loadStatus) {
|
||||
try {
|
||||
|
||||
EmailContent.Body body = EmailContent.Body.restoreBodyWithMessageId(context,
|
||||
localMessage.mId);
|
||||
if (body == null) {
|
||||
body = new EmailContent.Body();
|
||||
}
|
||||
try {
|
||||
// Copy the fields that are available into the message object
|
||||
LegacyConversions.updateMessageFields(localMessage, message,
|
||||
localMessage.mAccountKey, localMessage.mMailboxKey);
|
||||
|
||||
// Now process body parts & attachments
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
|
||||
ConversionUtilities.updateBodyFields(body, localMessage, viewables);
|
||||
|
||||
// Commit the message & body to the local store immediately
|
||||
saveOrUpdate(localMessage, context);
|
||||
saveOrUpdate(body, context);
|
||||
|
||||
// process (and save) attachments
|
||||
LegacyConversions.updateAttachments(context, localMessage, attachments);
|
||||
|
||||
// One last update of message with two updated flags
|
||||
localMessage.mFlagLoaded = loadStatus;
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(EmailContent.MessageColumns.FLAG_ATTACHMENT, localMessage.mFlagAttachment);
|
||||
cv.put(EmailContent.MessageColumns.FLAG_LOADED, localMessage.mFlagLoaded);
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI,
|
||||
localMessage.mId);
|
||||
context.getContentResolver().update(uri, cv, null, null);
|
||||
|
||||
} catch (MessagingException me) {
|
||||
Log.e(Logging.LOG_TAG, "Error while copying downloaded message." + me);
|
||||
}
|
||||
|
||||
} catch (RuntimeException rte) {
|
||||
Log.e(Logging.LOG_TAG, "Error while storing downloaded message." + rte.toString());
|
||||
} catch (IOException ioe) {
|
||||
Log.e(Logging.LOG_TAG, "Error while storing attachment." + ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -975,10 +724,10 @@ public class ImapService extends Service {
|
|||
if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
|
||||
long accountKey = message.mAccountKey;
|
||||
String protocolSearchInfo = message.mProtocolSearchInfo;
|
||||
// if (accountKey == mLastSearchAccountKey &&
|
||||
// protocolSearchInfo.equals(mLastSearchServerId)) {
|
||||
// return mLastSearchRemoteMailbox;
|
||||
// }
|
||||
if (accountKey == mLastSearchAccountKey &&
|
||||
protocolSearchInfo.equals(mLastSearchServerId)) {
|
||||
return mLastSearchRemoteMailbox;
|
||||
}
|
||||
Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI,
|
||||
Mailbox.CONTENT_PROJECTION, Mailbox.PATH_AND_ACCOUNT_SELECTION,
|
||||
new String[] {protocolSearchInfo, Long.toString(accountKey)},
|
||||
|
@ -987,9 +736,9 @@ public class ImapService extends Service {
|
|||
if (c.moveToNext()) {
|
||||
Mailbox mailbox = new Mailbox();
|
||||
mailbox.restore(c);
|
||||
// mLastSearchAccountKey = accountKey;
|
||||
// mLastSearchServerId = protocolSearchInfo;
|
||||
// mLastSearchRemoteMailbox = mailbox;
|
||||
mLastSearchAccountKey = accountKey;
|
||||
mLastSearchServerId = protocolSearchInfo;
|
||||
mLastSearchRemoteMailbox = mailbox;
|
||||
return mailbox;
|
||||
} else {
|
||||
return null;
|
||||
|
@ -1574,22 +1323,6 @@ public class ImapService extends Service {
|
|||
remoteTrashFolder.close(false);
|
||||
}
|
||||
|
||||
/** Results of the latest synchronization. */
|
||||
private static class SyncResults {
|
||||
/** The total # of messages in the folder */
|
||||
public final int mTotalMessages;
|
||||
/** A list of new message IDs; must not be {@code null} */
|
||||
public final ArrayList<Long> mAddedMessages;
|
||||
|
||||
public SyncResults(int totalMessages, ArrayList<Long> addedMessages) {
|
||||
if (addedMessages == null) {
|
||||
throw new IllegalArgumentException("addedMessages must not be null");
|
||||
}
|
||||
mTotalMessages = totalMessages;
|
||||
mAddedMessages = addedMessages;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A message and numeric uid that's easily sortable
|
||||
*/
|
||||
|
@ -1686,7 +1419,7 @@ public class ImapService extends Service {
|
|||
LegacyConversions.updateMessageFields(localMessage,
|
||||
message, account.mId, mailbox.mId);
|
||||
// Commit the message to the local store
|
||||
saveOrUpdate(localMessage, context);
|
||||
Utilities.saveOrUpdate(localMessage, context);
|
||||
localMessage.mMailboxKey = destMailboxId;
|
||||
// We load 50k or so; maybe it's complete, maybe not...
|
||||
int flag = EmailContent.Message.FLAG_LOADED_COMPLETE;
|
||||
|
@ -1697,7 +1430,7 @@ public class ImapService extends Service {
|
|||
if (message.getSize() > Store.FETCH_BODY_SANE_SUGGESTED_SIZE) {
|
||||
flag = EmailContent.Message.FLAG_LOADED_PARTIAL;
|
||||
}
|
||||
copyOneMessageToProvider(context, message, localMessage, flag);
|
||||
Utilities.copyOneMessageToProvider(context, message, localMessage, flag);
|
||||
} catch (MessagingException me) {
|
||||
Log.e(Logging.LOG_TAG,
|
||||
"Error while copying downloaded message." + me);
|
||||
|
|
|
@ -18,99 +18,37 @@ package com.android.email.service;
|
|||
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncStatusObserver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.Controller;
|
||||
import com.android.email.Email;
|
||||
import com.android.email.Preferences;
|
||||
import com.android.email.SingleRunningTask;
|
||||
import com.android.email.provider.AccountReconciler;
|
||||
import com.android.emailcommon.AccountManagerTypes;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.utility.EmailAsyncTask;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Background service for refreshing non-push email accounts.
|
||||
*
|
||||
* TODO: Convert to IntentService to move *all* work off the UI thread, serialize work, and avoid
|
||||
* possible problems with out-of-order startId processing.
|
||||
* Legacy service, now used mainly for account reconciliation
|
||||
*/
|
||||
public class MailService extends Service {
|
||||
private static final String LOG_TAG = "Email-MailService";
|
||||
|
||||
private static final String ACTION_CHECK_MAIL =
|
||||
"com.android.email.intent.action.MAIL_SERVICE_WAKEUP";
|
||||
private static final String ACTION_RESCHEDULE =
|
||||
"com.android.email.intent.action.MAIL_SERVICE_RESCHEDULE";
|
||||
private static final String ACTION_CANCEL =
|
||||
"com.android.email.intent.action.MAIL_SERVICE_CANCEL";
|
||||
private static final String ACTION_SEND_PENDING_MAIL =
|
||||
"com.android.email.intent.action.MAIL_SERVICE_SEND_PENDING";
|
||||
|
||||
private static final String EXTRA_ACCOUNT = "com.android.email.intent.extra.ACCOUNT";
|
||||
private static final String EXTRA_ACCOUNT_INFO = "com.android.email.intent.extra.ACCOUNT_INFO";
|
||||
private static final String EXTRA_DEBUG_WATCHDOG = "com.android.email.intent.extra.WATCHDOG";
|
||||
|
||||
/** Time between watchdog checks; in milliseconds */
|
||||
private static final long WATCHDOG_DELAY = 10 * 60 * 1000; // 10 minutes
|
||||
|
||||
/** Sentinel value asking to update mSyncReports if it's currently empty */
|
||||
@VisibleForTesting
|
||||
static final int SYNC_REPORTS_ALL_ACCOUNTS_IF_EMPTY = -1;
|
||||
/** Sentinel value asking that mSyncReports be rebuilt */
|
||||
@VisibleForTesting
|
||||
static final int SYNC_REPORTS_RESET = -2;
|
||||
|
||||
@VisibleForTesting
|
||||
Controller mController;
|
||||
private final Controller.Result mControllerCallback = new ControllerResults();
|
||||
private ContentResolver mContentResolver;
|
||||
private Context mContext;
|
||||
|
||||
private int mStartId;
|
||||
|
||||
/**
|
||||
* Access must be synchronized, because there are accesses from the Controller callback
|
||||
*/
|
||||
/*package*/ static HashMap<Long,AccountSyncReport> mSyncReports =
|
||||
new HashMap<Long,AccountSyncReport>();
|
||||
|
||||
public static void actionReschedule(Context context) {
|
||||
Intent i = new Intent();
|
||||
i.setClass(context, MailService.class);
|
||||
i.setAction(MailService.ACTION_RESCHEDULE);
|
||||
context.startService(i);
|
||||
}
|
||||
|
||||
public static void actionCancel(Context context) {
|
||||
Intent i = new Intent();
|
||||
i.setClass(context, MailService.class);
|
||||
i.setAction(MailService.ACTION_CANCEL);
|
||||
context.startService(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for AttachmentDownloadService to ask that pending mail be sent
|
||||
|
@ -136,104 +74,21 @@ public class MailService extends Service {
|
|||
}
|
||||
});
|
||||
|
||||
// TODO this needs to be passed through the controller and back to us
|
||||
mStartId = startId;
|
||||
String action = intent.getAction();
|
||||
final long accountId = intent.getLongExtra(EXTRA_ACCOUNT, -1);
|
||||
|
||||
mController = Controller.getInstance(this);
|
||||
mController.addResultCallback(mControllerCallback);
|
||||
mContentResolver = getContentResolver();
|
||||
mContext = this;
|
||||
|
||||
final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
if ((ACTION_CHECK_MAIL).equals(action)) {
|
||||
// DB access required to satisfy this intent, so offload from UI thread
|
||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// If we have the data, restore the last-sync-times for each account
|
||||
// These are cached in the wakeup intent in case the process was killed.
|
||||
restoreSyncReports(intent);
|
||||
|
||||
// Sync a specific account if given
|
||||
if (Email.DEBUG) {
|
||||
Log.d(LOG_TAG, "action: check mail for id=" + accountId);
|
||||
}
|
||||
if (accountId >= 0) {
|
||||
setWatchdog(accountId, alarmManager);
|
||||
}
|
||||
|
||||
// Start sync if account is given && auto-sync is allowed
|
||||
boolean syncStarted = false;
|
||||
if (accountId != -1 && ContentResolver.getMasterSyncAutomatically()) {
|
||||
synchronized(mSyncReports) {
|
||||
for (AccountSyncReport report: mSyncReports.values()) {
|
||||
if (report.accountId == accountId) {
|
||||
// Only sync POP3 here (will remove POP3 sync soon)
|
||||
if (report.syncEnabled &&
|
||||
Account.getProtocol(MailService.this, accountId)
|
||||
.equals(HostAuth.SCHEME_POP3)) {
|
||||
syncStarted = syncOneAccount(mController, accountId,
|
||||
startId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reschedule if we didn't start sync.
|
||||
if (!syncStarted) {
|
||||
// Prevent runaway on the current account by pretending it updated
|
||||
if (accountId != -1) {
|
||||
updateAccountReport(accountId, 0);
|
||||
}
|
||||
// Find next account to sync, and reschedule
|
||||
reschedule(alarmManager);
|
||||
// Stop the service, unless actually syncing (which will stop the service)
|
||||
stopSelf(startId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (ACTION_CANCEL.equals(action)) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(LOG_TAG, "action: cancel");
|
||||
}
|
||||
cancel();
|
||||
stopSelf(startId);
|
||||
}
|
||||
else if (ACTION_SEND_PENDING_MAIL.equals(action)) {
|
||||
if (ACTION_SEND_PENDING_MAIL.equals(action)) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(LOG_TAG, "action: send pending mail");
|
||||
}
|
||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mController.sendPendingMessages(accountId);
|
||||
Controller.getInstance(getApplicationContext()).sendPendingMessages(accountId);
|
||||
}
|
||||
});
|
||||
stopSelf(startId);
|
||||
}
|
||||
else if (ACTION_RESCHEDULE.equals(action)) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(LOG_TAG, "action: reschedule");
|
||||
}
|
||||
// DB access required to satisfy this intent, so offload from UI thread
|
||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// When called externally, we refresh the sync reports table to pick up
|
||||
// any changes in the account list or account settings
|
||||
refreshSyncReports();
|
||||
// Finally, scan for the next needing update, and set an alarm for it
|
||||
reschedule(alarmManager);
|
||||
stopSelf(startId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Returning START_NOT_STICKY means that if a mail check is killed (e.g. due to memory
|
||||
// pressure, there will be no explicit restart. This is OK; Note that we set a watchdog
|
||||
|
@ -247,390 +102,6 @@ public class MailService extends Service {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
Controller.getInstance(getApplication()).removeResultCallback(mControllerCallback);
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
|
||||
PendingIntent pi = createAlarmIntent(-1, null, false);
|
||||
alarmMgr.cancel(pi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the sync reports, to pick up any changes in the account list or account settings.
|
||||
*/
|
||||
private void refreshSyncReports() {
|
||||
synchronized (mSyncReports) {
|
||||
// Make shallow copy of sync reports so we can recover the prev sync times
|
||||
HashMap<Long,AccountSyncReport> oldSyncReports =
|
||||
new HashMap<Long,AccountSyncReport>(mSyncReports);
|
||||
|
||||
// Delete the sync reports to force a refresh from live account db data
|
||||
setupSyncReportsLocked(SYNC_REPORTS_RESET, this);
|
||||
|
||||
// Restore prev-sync & next-sync times for any reports in the new list
|
||||
for (AccountSyncReport newReport : mSyncReports.values()) {
|
||||
AccountSyncReport oldReport = oldSyncReports.get(newReport.accountId);
|
||||
if (oldReport != null) {
|
||||
newReport.prevSyncTime = oldReport.prevSyncTime;
|
||||
newReport.setNextSyncTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an alarm with the entire list. This also sends a list of known last-sync
|
||||
* times with the alarm, so if we are killed between alarms, we don't lose this info.
|
||||
*
|
||||
* @param alarmMgr passed in so we can mock for testing.
|
||||
*/
|
||||
private void reschedule(AlarmManager alarmMgr) {
|
||||
// restore the reports if lost
|
||||
setupSyncReports(SYNC_REPORTS_ALL_ACCOUNTS_IF_EMPTY);
|
||||
synchronized (mSyncReports) {
|
||||
int numAccounts = mSyncReports.size();
|
||||
long[] accountInfo = new long[numAccounts * 2]; // pairs of { accountId, lastSync }
|
||||
int accountInfoIndex = 0;
|
||||
|
||||
long nextCheckTime = Long.MAX_VALUE;
|
||||
AccountSyncReport nextAccount = null;
|
||||
long timeNow = SystemClock.elapsedRealtime();
|
||||
|
||||
for (AccountSyncReport report : mSyncReports.values()) {
|
||||
if (report.syncInterval <= 0) { // no timed checks - skip
|
||||
continue;
|
||||
}
|
||||
long prevSyncTime = report.prevSyncTime;
|
||||
long nextSyncTime = report.nextSyncTime;
|
||||
|
||||
// select next account to sync
|
||||
if ((prevSyncTime == 0) || (nextSyncTime < timeNow)) { // never checked, or overdue
|
||||
nextCheckTime = 0;
|
||||
nextAccount = report;
|
||||
} else if (nextSyncTime < nextCheckTime) { // next to be checked
|
||||
nextCheckTime = nextSyncTime;
|
||||
nextAccount = report;
|
||||
}
|
||||
// collect last-sync-times for all accounts
|
||||
// this is using pairs of {long,long} to simplify passing in a bundle
|
||||
accountInfo[accountInfoIndex++] = report.accountId;
|
||||
accountInfo[accountInfoIndex++] = report.prevSyncTime;
|
||||
}
|
||||
|
||||
// Clear out any unused elements in the array
|
||||
while (accountInfoIndex < accountInfo.length) {
|
||||
accountInfo[accountInfoIndex++] = -1;
|
||||
}
|
||||
|
||||
// set/clear alarm as needed
|
||||
long idToCheck = (nextAccount == null) ? -1 : nextAccount.accountId;
|
||||
PendingIntent pi = createAlarmIntent(idToCheck, accountInfo, false);
|
||||
|
||||
if (nextAccount == null) {
|
||||
alarmMgr.cancel(pi);
|
||||
if (Email.DEBUG) {
|
||||
Log.d(LOG_TAG, "reschedule: alarm cancel - no account to check");
|
||||
}
|
||||
} else {
|
||||
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextCheckTime, pi);
|
||||
if (Email.DEBUG) {
|
||||
Log.d(LOG_TAG, "reschedule: alarm set at " + nextCheckTime
|
||||
+ " for " + nextAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a watchdog alarm and set it. This is used in case a mail check fails (e.g. we are
|
||||
* killed by the system due to memory pressure.) Normally, a mail check will complete and
|
||||
* the watchdog will be replaced by the call to reschedule().
|
||||
* @param accountId the account we were trying to check
|
||||
* @param alarmMgr system alarm manager
|
||||
*/
|
||||
private void setWatchdog(long accountId, AlarmManager alarmMgr) {
|
||||
PendingIntent pi = createAlarmIntent(accountId, null, true);
|
||||
long timeNow = SystemClock.elapsedRealtime();
|
||||
long nextCheckTime = timeNow + WATCHDOG_DELAY;
|
||||
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextCheckTime, pi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a pending intent for use by this alarm. Most of the fields must be the same
|
||||
* (in order for the intent to be recognized by the alarm manager) but the extras can
|
||||
* be different, and are passed in here as parameters.
|
||||
*/
|
||||
private PendingIntent createAlarmIntent(long checkId, long[] accountInfo, boolean isWatchdog) {
|
||||
Intent i = new Intent();
|
||||
i.setClass(this, MailService.class);
|
||||
i.setAction(ACTION_CHECK_MAIL);
|
||||
i.putExtra(EXTRA_ACCOUNT, checkId);
|
||||
i.putExtra(EXTRA_ACCOUNT_INFO, accountInfo);
|
||||
if (isWatchdog) {
|
||||
i.putExtra(EXTRA_DEBUG_WATCHDOG, true);
|
||||
}
|
||||
PendingIntent pi = PendingIntent.getService(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
return pi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a controller sync for a specific account
|
||||
*
|
||||
* @param controller The controller to do the sync work
|
||||
* @param checkAccountId the account Id to try and check
|
||||
* @param startId the id of this service launch
|
||||
* @return true if mail checking has started, false if it could not (e.g. bad account id)
|
||||
*/
|
||||
private boolean syncOneAccount(Controller controller, long checkAccountId, int startId) {
|
||||
long inboxId = Mailbox.findMailboxOfType(this, checkAccountId, Mailbox.TYPE_INBOX);
|
||||
if (inboxId == Mailbox.NO_MAILBOX) {
|
||||
return false;
|
||||
} else {
|
||||
controller.serviceCheckMail(checkAccountId, inboxId, startId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Times are relative to SystemClock.elapsedRealtime()
|
||||
*
|
||||
* TODO: Look more closely at syncEnabled and see if we can simply coalesce it into
|
||||
* syncInterval (e.g. if !syncEnabled, set syncInterval to -1).
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static class AccountSyncReport {
|
||||
long accountId;
|
||||
/** The time of the last sync, or, {@code 0}, the last sync time is unknown. */
|
||||
long prevSyncTime;
|
||||
/** The time of the next sync. If {@code 0}, sync ASAP. If {@code 1}, don't sync. */
|
||||
long nextSyncTime;
|
||||
/** Minimum time between syncs; in minutes. */
|
||||
int syncInterval;
|
||||
/** If {@code true}, auto sync is enabled. */
|
||||
boolean syncEnabled;
|
||||
|
||||
/**
|
||||
* Sets the next sync time using the previous sync time and sync interval.
|
||||
*/
|
||||
private void setNextSyncTime() {
|
||||
if (syncInterval > 0 && prevSyncTime != 0) {
|
||||
nextSyncTime = prevSyncTime + (syncInterval * 1000 * 60);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "id=" + accountId + " prevSync=" + prevSyncTime + " nextSync=" + nextSyncTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* scan accounts to create a list of { acct, prev sync, next sync, #new }
|
||||
* use this to create a fresh copy. assumes all accounts need sync
|
||||
*
|
||||
* @param accountId -1 will rebuild the list if empty. other values will force loading
|
||||
* of a single account (e.g if it was created after the original list population)
|
||||
*/
|
||||
private void setupSyncReports(long accountId) {
|
||||
synchronized (mSyncReports) {
|
||||
setupSyncReportsLocked(accountId, mContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the work of setupSyncReports. Must be synchronized on mSyncReports.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void setupSyncReportsLocked(long accountId, Context context) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
if (accountId == SYNC_REPORTS_RESET) {
|
||||
// For test purposes, force refresh of mSyncReports
|
||||
mSyncReports.clear();
|
||||
accountId = SYNC_REPORTS_ALL_ACCOUNTS_IF_EMPTY;
|
||||
} else if (accountId == SYNC_REPORTS_ALL_ACCOUNTS_IF_EMPTY) {
|
||||
// -1 == reload the list if empty, otherwise exit immediately
|
||||
if (mSyncReports.size() > 0) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// load a single account if it doesn't already have a sync record
|
||||
if (mSyncReports.containsKey(accountId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// setup to add a single account or all accounts
|
||||
Uri uri;
|
||||
if (accountId == SYNC_REPORTS_ALL_ACCOUNTS_IF_EMPTY) {
|
||||
uri = Account.CONTENT_URI;
|
||||
} else {
|
||||
uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
|
||||
}
|
||||
|
||||
final boolean oneMinuteRefresh
|
||||
= Preferences.getPreferences(this).getForceOneMinuteRefresh();
|
||||
if (oneMinuteRefresh) {
|
||||
Log.w(LOG_TAG, "One-minute refresh enabled.");
|
||||
}
|
||||
|
||||
// We use a full projection here because we'll restore each account object from it
|
||||
Cursor c = resolver.query(uri, Account.CONTENT_PROJECTION, null, null, null);
|
||||
try {
|
||||
while (c.moveToNext()) {
|
||||
Account account = Account.getContent(c, Account.class);
|
||||
// The following sanity checks are primarily for the sake of ignoring non-user
|
||||
// accounts that may have been left behind e.g. by failed unit tests.
|
||||
// Properly-formed accounts will always pass these simple checks.
|
||||
if (TextUtils.isEmpty(account.mEmailAddress)
|
||||
|| account.mHostAuthKeyRecv <= 0
|
||||
|| account.mHostAuthKeySend <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The account is OK, so proceed
|
||||
AccountSyncReport report = new AccountSyncReport();
|
||||
int syncInterval = account.mSyncInterval;
|
||||
|
||||
// If we're not using MessagingController (EAS at this point), don't schedule syncs
|
||||
if (!mController.isMessagingController(account.mId)) {
|
||||
syncInterval = Account.CHECK_INTERVAL_NEVER;
|
||||
} else if (oneMinuteRefresh && syncInterval >= 0) {
|
||||
syncInterval = 1;
|
||||
}
|
||||
|
||||
report.accountId = account.mId;
|
||||
report.prevSyncTime = 0;
|
||||
report.nextSyncTime = (syncInterval > 0) ? 0 : -1; // 0 == ASAP -1 == no sync
|
||||
|
||||
report.syncInterval = syncInterval;
|
||||
|
||||
// See if the account is enabled for sync in AccountManager
|
||||
android.accounts.Account accountManagerAccount =
|
||||
new android.accounts.Account(account.mEmailAddress,
|
||||
AccountManagerTypes.TYPE_POP_IMAP);
|
||||
report.syncEnabled = ContentResolver.getSyncAutomatically(accountManagerAccount,
|
||||
EmailContent.AUTHORITY);
|
||||
|
||||
// TODO lookup # new in inbox
|
||||
mSyncReports.put(report.accountId, report);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update list with a single account's sync times and unread count
|
||||
*
|
||||
* @param accountId the account being updated
|
||||
* @param newCount the number of new messages, or -1 if not being reported (don't update)
|
||||
* @return the report for the updated account, or null if it doesn't exist (e.g. deleted)
|
||||
*/
|
||||
private AccountSyncReport updateAccountReport(long accountId, int newCount) {
|
||||
// restore the reports if lost
|
||||
setupSyncReports(accountId);
|
||||
synchronized (mSyncReports) {
|
||||
AccountSyncReport report = mSyncReports.get(accountId);
|
||||
if (report == null) {
|
||||
// discard result - there is no longer an account with this id
|
||||
Log.d(LOG_TAG, "No account to update for id=" + Long.toString(accountId));
|
||||
return null;
|
||||
}
|
||||
|
||||
// report found - update it (note - editing the report while in-place in the hashmap)
|
||||
report.prevSyncTime = SystemClock.elapsedRealtime();
|
||||
report.setNextSyncTime();
|
||||
if (Email.DEBUG) {
|
||||
Log.d(LOG_TAG, "update account " + report.toString());
|
||||
}
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* when we receive an alarm, update the account sync reports list if necessary
|
||||
* this will be the case when if we have restarted the process and lost the data
|
||||
* in the global.
|
||||
*
|
||||
* @param restoreIntent the intent with the list
|
||||
*/
|
||||
private void restoreSyncReports(Intent restoreIntent) {
|
||||
// restore the reports if lost
|
||||
setupSyncReports(SYNC_REPORTS_ALL_ACCOUNTS_IF_EMPTY);
|
||||
synchronized (mSyncReports) {
|
||||
long[] accountInfo = restoreIntent.getLongArrayExtra(EXTRA_ACCOUNT_INFO);
|
||||
if (accountInfo == null) {
|
||||
Log.d(LOG_TAG, "no data in intent to restore");
|
||||
return;
|
||||
}
|
||||
int accountInfoIndex = 0;
|
||||
int accountInfoLimit = accountInfo.length;
|
||||
while (accountInfoIndex < accountInfoLimit) {
|
||||
long accountId = accountInfo[accountInfoIndex++];
|
||||
long prevSync = accountInfo[accountInfoIndex++];
|
||||
AccountSyncReport report = mSyncReports.get(accountId);
|
||||
if (report != null) {
|
||||
if (report.prevSyncTime == 0) {
|
||||
report.prevSyncTime = prevSync;
|
||||
report.setNextSyncTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ControllerResults extends Controller.Result {
|
||||
@Override
|
||||
public void updateMailboxCallback(MessagingException result, long accountId,
|
||||
long mailboxId, int progress, int numNewMessages,
|
||||
ArrayList<Long> addedMessages) {
|
||||
// First, look for authentication failures and notify
|
||||
//checkAuthenticationStatus(result, accountId);
|
||||
if (result != null || progress == 100) {
|
||||
// We only track the inbox here in the service - ignore other mailboxes
|
||||
long inboxId = Mailbox.findMailboxOfType(MailService.this,
|
||||
accountId, Mailbox.TYPE_INBOX);
|
||||
if (mailboxId == inboxId) {
|
||||
if (progress == 100) {
|
||||
updateAccountReport(accountId, numNewMessages);
|
||||
} else {
|
||||
updateAccountReport(accountId, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serviceCheckMailCallback(MessagingException result, long accountId,
|
||||
long mailboxId, int progress, long tag) {
|
||||
if (result != null || progress == 100) {
|
||||
if (result != null) {
|
||||
// the checkmail ended in an error. force an update of the refresh
|
||||
// time, so we don't just spin on this account
|
||||
updateAccountReport(accountId, -1);
|
||||
}
|
||||
AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
|
||||
reschedule(alarmManager);
|
||||
int serviceId = mStartId;
|
||||
if (tag != 0) {
|
||||
serviceId = (int) tag;
|
||||
}
|
||||
stopSelf(serviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class EmailSyncStatusObserver implements SyncStatusObserver {
|
||||
@Override
|
||||
public void onStatusChanged(int which) {
|
||||
// We ignore the argument (we can only get called in one case - when settings change)
|
||||
}
|
||||
}
|
||||
|
||||
public static ArrayList<Account> getPopImapAccountList(Context context) {
|
||||
ArrayList<Account> providerAccounts = new ArrayList<Account>();
|
||||
Cursor c = context.getContentResolver().query(Account.CONTENT_URI, Account.ID_PROJECTION,
|
||||
|
|
|
@ -0,0 +1,722 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.Email;
|
||||
import com.android.email.LegacyConversions;
|
||||
import com.android.email.NotificationController;
|
||||
import com.android.email.mail.Store;
|
||||
import com.android.email.provider.Utilities;
|
||||
import com.android.emailcommon.AccountManagerTypes;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.TrafficFlags;
|
||||
import com.android.emailcommon.internet.MimeUtility;
|
||||
import com.android.emailcommon.mail.AuthenticationFailedException;
|
||||
import com.android.emailcommon.mail.FetchProfile;
|
||||
import com.android.emailcommon.mail.Flag;
|
||||
import com.android.emailcommon.mail.Folder;
|
||||
import com.android.emailcommon.mail.Folder.FolderType;
|
||||
import com.android.emailcommon.mail.Folder.MessageRetrievalListener;
|
||||
import com.android.emailcommon.mail.Folder.OpenMode;
|
||||
import com.android.emailcommon.mail.Message;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.mail.Part;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||
import com.android.emailcommon.utility.AttachmentUtilities;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class Pop3Service extends Service {
|
||||
private static final String TAG = "Pop3Service";
|
||||
private static final int MAX_SMALL_MESSAGE_SIZE = (25 * 1024);
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
// Callbacks as set up via setCallback
|
||||
private static final RemoteCallbackList<IEmailServiceCallback> mCallbackList =
|
||||
new RemoteCallbackList<IEmailServiceCallback>();
|
||||
|
||||
private interface ServiceCallbackWrapper {
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy that can be used by various sync adapters to tie into ExchangeService's callback system
|
||||
* Used this way: ExchangeService.callback().callbackMethod(args...);
|
||||
* The proxy wraps checking for existence of a ExchangeService instance
|
||||
* Failures of these callbacks can be safely ignored.
|
||||
*/
|
||||
static private final IEmailServiceCallback.Stub sCallbackProxy =
|
||||
new IEmailServiceCallback.Stub() {
|
||||
|
||||
/**
|
||||
* Broadcast a callback to the everyone that's registered
|
||||
*
|
||||
* @param wrapper the ServiceCallbackWrapper used in the broadcast
|
||||
*/
|
||||
private synchronized void broadcastCallback(ServiceCallbackWrapper wrapper) {
|
||||
RemoteCallbackList<IEmailServiceCallback> callbackList = mCallbackList;
|
||||
if (callbackList != null) {
|
||||
// Call everyone on our callback list
|
||||
int count = callbackList.beginBroadcast();
|
||||
try {
|
||||
for (int i = 0; i < count; i++) {
|
||||
try {
|
||||
wrapper.call(callbackList.getBroadcastItem(i));
|
||||
} catch (RemoteException e) {
|
||||
// Safe to ignore
|
||||
} catch (RuntimeException e) {
|
||||
// We don't want an exception in one call to prevent other calls, so
|
||||
// we'll just log this and continue
|
||||
Log.e(TAG, "Caught RuntimeException in broadcast", e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// No matter what, we need to finish the broadcast
|
||||
callbackList.finishBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentStatus(final long messageId, final long attachmentId,
|
||||
final int status, final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.loadAttachmentStatus(messageId, attachmentId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessageStatus(final long messageId, final int status, final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.loadMessageStatus(messageId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageStatus(final long messageId, final String subject, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.sendMessageStatus(messageId, subject, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxListStatus(final long accountId, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.syncMailboxListStatus(accountId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxStatus(final long mailboxId, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.syncMailboxStatus(mailboxId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create our EmailService implementation here.
|
||||
*/
|
||||
private final EmailServiceStub mBinder = new EmailServiceStub() {
|
||||
|
||||
@Override
|
||||
public void startSync(long mailboxId, boolean userRequest) throws RemoteException {
|
||||
Context context = getApplicationContext();
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
|
||||
if (mailbox == null) return;
|
||||
Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
|
||||
if (account == null) return;
|
||||
android.accounts.Account acct = new android.accounts.Account(account.mEmailAddress,
|
||||
AccountManagerTypes.TYPE_POP_IMAP);
|
||||
Log.d(TAG, "startSync API requesting sync");
|
||||
Bundle extras = new Bundle();
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
||||
ContentResolver.requestSync(acct, EmailContent.AUTHORITY, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(IEmailServiceCallback cb) throws RemoteException {
|
||||
mCallbackList.register(cb);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
mBinder.init(this, sCallbackProxy);
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start foreground synchronization of the specified folder. This is called by
|
||||
* synchronizeMailbox or checkMail.
|
||||
* TODO this should use ID's instead of fully-restored objects
|
||||
* @param account
|
||||
* @param folder
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public static void synchronizeMailboxSynchronous(Context context, final Account account,
|
||||
final Mailbox folder) throws MessagingException {
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account));
|
||||
if ((folder.mFlags & Mailbox.FLAG_HOLDS_MAIL) == 0) {
|
||||
}
|
||||
NotificationController nc = NotificationController.getInstance(context);
|
||||
try {
|
||||
processPendingActionsSynchronous(context, account);
|
||||
synchronizeMailboxGeneric(context, account, folder);
|
||||
// Clear authentication notification for this account
|
||||
nc.cancelLoginFailedNotification(account.mId);
|
||||
} catch (MessagingException e) {
|
||||
if (Logging.LOGD) {
|
||||
Log.v(Logging.LOG_TAG, "synchronizeMailbox", e);
|
||||
}
|
||||
if (e instanceof AuthenticationFailedException) {
|
||||
// Generate authentication notification
|
||||
nc.showLoginFailedNotification(account.mId);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight record for the first pass of message sync, where I'm just seeing if
|
||||
* the local message requires sync. Later (for messages that need syncing) we'll do a full
|
||||
* readout from the DB.
|
||||
*/
|
||||
private static class LocalMessageInfo {
|
||||
private static final int COLUMN_ID = 0;
|
||||
private static final int COLUMN_FLAG_READ = 1;
|
||||
private static final int COLUMN_FLAG_FAVORITE = 2;
|
||||
private static final int COLUMN_FLAG_LOADED = 3;
|
||||
private static final int COLUMN_SERVER_ID = 4;
|
||||
private static final int COLUMN_FLAGS = 7;
|
||||
private static final String[] PROJECTION = new String[] {
|
||||
EmailContent.RECORD_ID,
|
||||
MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_LOADED,
|
||||
SyncColumns.SERVER_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY,
|
||||
MessageColumns.FLAGS
|
||||
};
|
||||
|
||||
final long mId;
|
||||
final boolean mFlagRead;
|
||||
final boolean mFlagFavorite;
|
||||
final int mFlagLoaded;
|
||||
final String mServerId;
|
||||
final int mFlags;
|
||||
|
||||
public LocalMessageInfo(Cursor c) {
|
||||
mId = c.getLong(COLUMN_ID);
|
||||
mFlagRead = c.getInt(COLUMN_FLAG_READ) != 0;
|
||||
mFlagFavorite = c.getInt(COLUMN_FLAG_FAVORITE) != 0;
|
||||
mFlagLoaded = c.getInt(COLUMN_FLAG_LOADED);
|
||||
mServerId = c.getString(COLUMN_SERVER_ID);
|
||||
mFlags = c.getInt(COLUMN_FLAGS);
|
||||
// Note: mailbox key and account key not needed - they are projected for the SELECT
|
||||
}
|
||||
}
|
||||
|
||||
private static void saveOrUpdate(EmailContent content, Context context) {
|
||||
if (content.isSaved()) {
|
||||
content.update(context, content.toContentValues());
|
||||
} else {
|
||||
content.save(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the structure and body of messages not yet synced
|
||||
* @param account the account we're syncing
|
||||
* @param remoteFolder the (open) Folder we're working on
|
||||
* @param unsyncedMessages an array of Message's we've got headers for
|
||||
* @param toMailbox the destination mailbox we're syncing
|
||||
* @throws MessagingException
|
||||
*/
|
||||
static void loadUnsyncedMessages(final Context context, final Account account,
|
||||
Folder remoteFolder, ArrayList<Message> unsyncedMessages, final Mailbox toMailbox)
|
||||
throws MessagingException {
|
||||
|
||||
// 1. Divide the unsynced messages into small & large (by size)
|
||||
|
||||
// TODO doing this work here (synchronously) is problematic because it prevents the UI
|
||||
// from affecting the order (e.g. download a message because the user requested it.) Much
|
||||
// of this logic should move out to a different sync loop that attempts to update small
|
||||
// groups of messages at a time, as a background task. However, we can't just return
|
||||
// (yet) because POP messages don't have an envelope yet....
|
||||
|
||||
ArrayList<Message> largeMessages = new ArrayList<Message>();
|
||||
ArrayList<Message> smallMessages = new ArrayList<Message>();
|
||||
for (Message message : unsyncedMessages) {
|
||||
if (message.getSize() > (MAX_SMALL_MESSAGE_SIZE)) {
|
||||
largeMessages.add(message);
|
||||
} else {
|
||||
smallMessages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Download small messages
|
||||
|
||||
// TODO Problems with this implementation. 1. For IMAP, where we get a real envelope,
|
||||
// this is going to be inefficient and duplicate work we've already done. 2. It's going
|
||||
// back to the DB for a local message that we already had (and discarded).
|
||||
|
||||
// For small messages, we specify "body", which returns everything (incl. attachments)
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), fp,
|
||||
new MessageRetrievalListener() {
|
||||
@Override
|
||||
public void messageRetrieved(Message message) {
|
||||
// Store the updated message locally and mark it fully loaded
|
||||
Utilities.copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentProgress(int progress) {
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Download large messages. We ask the server to give us the message structure,
|
||||
// but not all of the attachments.
|
||||
fp.clear();
|
||||
fp.add(FetchProfile.Item.STRUCTURE);
|
||||
remoteFolder.fetch(largeMessages.toArray(new Message[largeMessages.size()]), fp, null);
|
||||
for (Message message : largeMessages) {
|
||||
if (message.getBody() == null) {
|
||||
// POP doesn't support STRUCTURE mode, so we'll just do a partial download
|
||||
// (hopefully enough to see some/all of the body) and mark the message for
|
||||
// further download.
|
||||
fp.clear();
|
||||
fp.add(FetchProfile.Item.BODY_SANE);
|
||||
// TODO a good optimization here would be to make sure that all Stores set
|
||||
// the proper size after this fetch and compare the before and after size. If
|
||||
// they equal we can mark this SYNCHRONIZED instead of PARTIALLY_SYNCHRONIZED
|
||||
remoteFolder.fetch(new Message[] { message }, fp, null);
|
||||
|
||||
// Store the partially-loaded message and mark it partially loaded
|
||||
Utilities.copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
EmailContent.Message.FLAG_LOADED_PARTIAL);
|
||||
} else {
|
||||
// We have a structure to deal with, from which
|
||||
// we can pull down the parts we want to actually store.
|
||||
// Build a list of parts we are interested in. Text parts will be downloaded
|
||||
// right now, attachments will be left for later.
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
// Download the viewables immediately
|
||||
for (Part part : viewables) {
|
||||
fp.clear();
|
||||
fp.add(part);
|
||||
// TODO what happens if the network connection dies? We've got partial
|
||||
// messages with incorrect status stored.
|
||||
remoteFolder.fetch(new Message[] { message }, fp, null);
|
||||
}
|
||||
// Store the updated message locally and mark it fully loaded
|
||||
Utilities.copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void downloadFlagAndEnvelope(final Context context, final Account account,
|
||||
final Mailbox mailbox, Folder remoteFolder, ArrayList<Message> unsyncedMessages,
|
||||
HashMap<String, LocalMessageInfo> localMessageMap, final ArrayList<Long> unseenMessages)
|
||||
throws MessagingException {
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.FLAGS);
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
|
||||
final HashMap<String, LocalMessageInfo> localMapCopy;
|
||||
if (localMessageMap != null)
|
||||
localMapCopy = new HashMap<String, LocalMessageInfo>(localMessageMap);
|
||||
else {
|
||||
localMapCopy = new HashMap<String, LocalMessageInfo>();
|
||||
}
|
||||
|
||||
remoteFolder.fetch(unsyncedMessages.toArray(new Message[0]), fp,
|
||||
new MessageRetrievalListener() {
|
||||
@Override
|
||||
public void messageRetrieved(Message message) {
|
||||
try {
|
||||
// Determine if the new message was already known (e.g. partial)
|
||||
// And create or reload the full message info
|
||||
LocalMessageInfo localMessageInfo =
|
||||
localMapCopy.get(message.getUid());
|
||||
EmailContent.Message localMessage = null;
|
||||
if (localMessageInfo == null) {
|
||||
localMessage = new EmailContent.Message();
|
||||
} else {
|
||||
localMessage = EmailContent.Message.restoreMessageWithId(
|
||||
context, localMessageInfo.mId);
|
||||
}
|
||||
|
||||
if (localMessage != null) {
|
||||
try {
|
||||
// Copy the fields that are available into the message
|
||||
LegacyConversions.updateMessageFields(localMessage,
|
||||
message, account.mId, mailbox.mId);
|
||||
// Commit the message to the local store
|
||||
saveOrUpdate(localMessage, context);
|
||||
// Track the "new" ness of the downloaded message
|
||||
if (!message.isSet(Flag.SEEN) && unseenMessages != null) {
|
||||
unseenMessages.add(localMessage.mId);
|
||||
}
|
||||
} catch (MessagingException me) {
|
||||
Log.e(Logging.LOG_TAG,
|
||||
"Error while copying downloaded message." + me);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.e(Logging.LOG_TAG,
|
||||
"Error while storing downloaded message." + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentProgress(int progress) {
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizer for IMAP.
|
||||
*
|
||||
* TODO Break this method up into smaller chunks.
|
||||
*
|
||||
* @param account the account to sync
|
||||
* @param mailbox the mailbox to sync
|
||||
* @return results of the sync pass
|
||||
* @throws MessagingException
|
||||
*/
|
||||
private static void synchronizeMailboxGeneric(final Context context,
|
||||
final Account account, final Mailbox mailbox) throws MessagingException {
|
||||
|
||||
/*
|
||||
* A list of IDs for messages that were downloaded and did not have the seen flag set.
|
||||
* This serves as the "true" new message count reported to the user via notification.
|
||||
*/
|
||||
final ArrayList<Long> unseenMessages = new ArrayList<Long>();
|
||||
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Logging.LOG_TAG, "*** synchronizeMailboxGeneric ***");
|
||||
}
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
// 0. We do not ever sync DRAFTS or OUTBOX (down or up)
|
||||
if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Get the message list from the local store and create an index of the uids
|
||||
|
||||
Cursor localUidCursor = null;
|
||||
HashMap<String, LocalMessageInfo> localMessageMap = new HashMap<String, LocalMessageInfo>();
|
||||
|
||||
try {
|
||||
localUidCursor = resolver.query(
|
||||
EmailContent.Message.CONTENT_URI,
|
||||
LocalMessageInfo.PROJECTION,
|
||||
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
|
||||
" AND " + MessageColumns.MAILBOX_KEY + "=?",
|
||||
new String[] {
|
||||
String.valueOf(account.mId),
|
||||
String.valueOf(mailbox.mId)
|
||||
},
|
||||
null);
|
||||
while (localUidCursor.moveToNext()) {
|
||||
LocalMessageInfo info = new LocalMessageInfo(localUidCursor);
|
||||
localMessageMap.put(info.mServerId, info);
|
||||
}
|
||||
} finally {
|
||||
if (localUidCursor != null) {
|
||||
localUidCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Open the remote folder and create the remote folder if necessary
|
||||
|
||||
Store remoteStore = Store.getInstance(account, context);
|
||||
// The account might have been deleted
|
||||
if (remoteStore == null) return;
|
||||
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
||||
|
||||
/*
|
||||
* If the folder is a "special" folder we need to see if it exists
|
||||
* on the remote server. It if does not exist we'll try to create it. If we
|
||||
* can't create we'll abort. This will happen on every single Pop3 folder as
|
||||
* designed and on Imap folders during error conditions. This allows us
|
||||
* to treat Pop3 and Imap the same in this code.
|
||||
*/
|
||||
if (mailbox.mType == Mailbox.TYPE_TRASH || mailbox.mType == Mailbox.TYPE_SENT
|
||||
|| mailbox.mType == Mailbox.TYPE_DRAFTS) {
|
||||
if (!remoteFolder.exists()) {
|
||||
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3, Open the remote folder. This pre-loads certain metadata like message count.
|
||||
remoteFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
// 4. Trash any remote messages that are marked as trashed locally.
|
||||
// TODO - this comment was here, but no code was here.
|
||||
|
||||
// 5. Get the remote message count.
|
||||
int remoteMessageCount = remoteFolder.getMessageCount();
|
||||
|
||||
// 6. Determine the limit # of messages to download
|
||||
int visibleLimit = mailbox.mVisibleLimit;
|
||||
if (visibleLimit <= 0) {
|
||||
visibleLimit = Email.VISIBLE_LIMIT_DEFAULT;
|
||||
}
|
||||
|
||||
// 7. Create a list of messages to download
|
||||
Message[] remoteMessages = new Message[0];
|
||||
final ArrayList<Message> unsyncedMessages = new ArrayList<Message>();
|
||||
HashMap<String, Message> remoteUidMap = new HashMap<String, Message>();
|
||||
|
||||
if (remoteMessageCount > 0) {
|
||||
/*
|
||||
* Message numbers start at 1.
|
||||
*/
|
||||
int remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1;
|
||||
int remoteEnd = remoteMessageCount;
|
||||
remoteMessages = remoteFolder.getMessages(remoteStart, remoteEnd, null);
|
||||
// TODO Why are we running through the list twice? Combine w/ for loop below
|
||||
for (Message message : remoteMessages) {
|
||||
remoteUidMap.put(message.getUid(), message);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a list of the messages that are in the remote list but not on the
|
||||
* local store, or messages that are in the local store but failed to download
|
||||
* on the last sync. These are the new messages that we will download.
|
||||
* Note, we also skip syncing messages which are flagged as "deleted message" sentinels,
|
||||
* because they are locally deleted and we don't need or want the old message from
|
||||
* the server.
|
||||
*/
|
||||
for (Message message : remoteMessages) {
|
||||
LocalMessageInfo localMessage = localMessageMap.get(message.getUid());
|
||||
// localMessage == null -> message has never been created (not even headers)
|
||||
// mFlagLoaded = UNLOADED -> message created, but none of body loaded
|
||||
// mFlagLoaded = PARTIAL -> message created, a "sane" amt of body has been loaded
|
||||
// mFlagLoaded = COMPLETE -> message body has been completely loaded
|
||||
// mFlagLoaded = DELETED -> message has been deleted
|
||||
// Only the first two of these are "unsynced", so let's retrieve them
|
||||
if (localMessage == null ||
|
||||
(localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_UNLOADED)) {
|
||||
unsyncedMessages.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Download basic info about the new/unloaded messages (if any)
|
||||
/*
|
||||
* Fetch the flags and envelope only of the new messages. This is intended to get us
|
||||
* critical data as fast as possible, and then we'll fill in the details.
|
||||
*/
|
||||
if (unsyncedMessages.size() > 0) {
|
||||
downloadFlagAndEnvelope(context, account, mailbox, remoteFolder, unsyncedMessages,
|
||||
localMessageMap, unseenMessages);
|
||||
}
|
||||
|
||||
// 9. Refresh the flags for any messages in the local store that we didn't just download.
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.FLAGS);
|
||||
remoteFolder.fetch(remoteMessages, fp, null);
|
||||
boolean remoteSupportsSeen = false;
|
||||
boolean remoteSupportsFlagged = false;
|
||||
boolean remoteSupportsAnswered = false;
|
||||
for (Flag flag : remoteFolder.getPermanentFlags()) {
|
||||
if (flag == Flag.SEEN) {
|
||||
remoteSupportsSeen = true;
|
||||
}
|
||||
if (flag == Flag.FLAGGED) {
|
||||
remoteSupportsFlagged = true;
|
||||
}
|
||||
if (flag == Flag.ANSWERED) {
|
||||
remoteSupportsAnswered = true;
|
||||
}
|
||||
}
|
||||
// Update SEEN/FLAGGED/ANSWERED (star) flags (if supported remotely - e.g. not for POP3)
|
||||
if (remoteSupportsSeen || remoteSupportsFlagged || remoteSupportsAnswered) {
|
||||
for (Message remoteMessage : remoteMessages) {
|
||||
LocalMessageInfo localMessageInfo = localMessageMap.get(remoteMessage.getUid());
|
||||
if (localMessageInfo == null) {
|
||||
continue;
|
||||
}
|
||||
boolean localSeen = localMessageInfo.mFlagRead;
|
||||
boolean remoteSeen = remoteMessage.isSet(Flag.SEEN);
|
||||
boolean newSeen = (remoteSupportsSeen && (remoteSeen != localSeen));
|
||||
boolean localFlagged = localMessageInfo.mFlagFavorite;
|
||||
boolean remoteFlagged = remoteMessage.isSet(Flag.FLAGGED);
|
||||
boolean newFlagged = (remoteSupportsFlagged && (localFlagged != remoteFlagged));
|
||||
int localFlags = localMessageInfo.mFlags;
|
||||
boolean localAnswered = (localFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0;
|
||||
boolean remoteAnswered = remoteMessage.isSet(Flag.ANSWERED);
|
||||
boolean newAnswered = (remoteSupportsAnswered && (localAnswered != remoteAnswered));
|
||||
if (newSeen || newFlagged || newAnswered) {
|
||||
Uri uri = ContentUris.withAppendedId(
|
||||
EmailContent.Message.CONTENT_URI, localMessageInfo.mId);
|
||||
ContentValues updateValues = new ContentValues();
|
||||
updateValues.put(MessageColumns.FLAG_READ, remoteSeen);
|
||||
updateValues.put(MessageColumns.FLAG_FAVORITE, remoteFlagged);
|
||||
if (remoteAnswered) {
|
||||
localFlags |= EmailContent.Message.FLAG_REPLIED_TO;
|
||||
} else {
|
||||
localFlags &= ~EmailContent.Message.FLAG_REPLIED_TO;
|
||||
}
|
||||
updateValues.put(MessageColumns.FLAGS, localFlags);
|
||||
resolver.update(uri, updateValues, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Remove any messages that are in the local store but no longer on the remote store.
|
||||
HashSet<String> localUidsToDelete = new HashSet<String>(localMessageMap.keySet());
|
||||
localUidsToDelete.removeAll(remoteUidMap.keySet());
|
||||
for (String uidToDelete : localUidsToDelete) {
|
||||
LocalMessageInfo infoToDelete = localMessageMap.get(uidToDelete);
|
||||
|
||||
// Delete associated data (attachment files)
|
||||
// Attachment & Body records are auto-deleted when we delete the Message record
|
||||
AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId,
|
||||
infoToDelete.mId);
|
||||
|
||||
// Delete the message itself
|
||||
Uri uriToDelete = ContentUris.withAppendedId(
|
||||
EmailContent.Message.CONTENT_URI, infoToDelete.mId);
|
||||
resolver.delete(uriToDelete, null, null);
|
||||
|
||||
// Delete extra rows (e.g. synced or deleted)
|
||||
Uri syncRowToDelete = ContentUris.withAppendedId(
|
||||
EmailContent.Message.UPDATED_CONTENT_URI, infoToDelete.mId);
|
||||
resolver.delete(syncRowToDelete, null, null);
|
||||
Uri deletERowToDelete = ContentUris.withAppendedId(
|
||||
EmailContent.Message.UPDATED_CONTENT_URI, infoToDelete.mId);
|
||||
resolver.delete(deletERowToDelete, null, null);
|
||||
}
|
||||
|
||||
loadUnsyncedMessages(context, account, remoteFolder, unsyncedMessages, mailbox);
|
||||
|
||||
// 14. Clean up and report results
|
||||
remoteFolder.close(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find messages in the updated table that need to be written back to server.
|
||||
*
|
||||
* Handles:
|
||||
* Read/Unread
|
||||
* Flagged
|
||||
* Append (upload)
|
||||
* Move To Trash
|
||||
* Empty trash
|
||||
* TODO:
|
||||
* Move
|
||||
*
|
||||
* @param account the account to scan for pending actions
|
||||
* @throws MessagingException
|
||||
*/
|
||||
private static void processPendingActionsSynchronous(Context context, Account account)
|
||||
throws MessagingException {
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account));
|
||||
String[] accountIdArgs = new String[] { Long.toString(account.mId) };
|
||||
|
||||
// Handle deletes first, it's always better to get rid of things first
|
||||
processPendingDeletesSynchronous(context, account, accountIdArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for messages that are in the Message_Deletes table, look for differences that
|
||||
* we can deal with, and do the work.
|
||||
*
|
||||
* @param account
|
||||
* @param resolver
|
||||
* @param accountIdArgs
|
||||
*/
|
||||
private static void processPendingDeletesSynchronous(Context context, Account account,
|
||||
String[] accountIdArgs) {
|
||||
Cursor deletes = context.getContentResolver().query(
|
||||
EmailContent.Message.DELETED_CONTENT_URI,
|
||||
EmailContent.Message.CONTENT_PROJECTION,
|
||||
EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs,
|
||||
EmailContent.MessageColumns.MAILBOX_KEY);
|
||||
try {
|
||||
// loop through messages marked as deleted
|
||||
while (deletes.moveToNext()) {
|
||||
EmailContent.Message oldMessage =
|
||||
EmailContent.getContent(deletes, EmailContent.Message.class);
|
||||
|
||||
// Finally, delete the update
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.DELETED_CONTENT_URI,
|
||||
oldMessage.mId);
|
||||
context.getContentResolver().delete(uri, null, null);
|
||||
}
|
||||
} finally {
|
||||
deletes.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
|||
import com.android.emailcommon.provider.EmailContent.Message;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.service.EmailServiceProxy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
@ -88,7 +89,11 @@ public class PopImapSyncAdapterService extends Service {
|
|||
Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
|
||||
if (account == null) return;
|
||||
try {
|
||||
ImapService.synchronizeMailboxSynchronous(context, account, mailbox);
|
||||
if (account.getProtocol(context).equals(HostAuth.SCHEME_IMAP)) {
|
||||
ImapService.synchronizeMailboxSynchronous(context, account, mailbox);
|
||||
} else {
|
||||
Pop3Service.synchronizeMailboxSynchronous(context, account, mailbox);
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
int cause = e.getExceptionType();
|
||||
switch(cause) {
|
||||
|
@ -117,52 +122,55 @@ public class PopImapSyncAdapterService extends Service {
|
|||
if (c != null && c.moveToNext()) {
|
||||
Account acct = new Account();
|
||||
acct.restore(c);
|
||||
String protocol = acct.getProtocol(context);
|
||||
if (protocol.equals(HostAuth.SCHEME_IMAP)) {
|
||||
if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD)) {
|
||||
Log.d(TAG, "Upload sync request for " + acct.mDisplayName);
|
||||
// See if any boxes have mail...
|
||||
Cursor updatesCursor = provider.query(Message.UPDATED_CONTENT_URI,
|
||||
new String[] {Message.MAILBOX_KEY},
|
||||
Message.ACCOUNT_KEY + "=?",
|
||||
new String[] {Long.toString(acct.mId)},
|
||||
null);
|
||||
if ((updatesCursor == null) || (updatesCursor.getCount() == 0)) return;
|
||||
ArrayList<Long> mailboxesToUpdate = new ArrayList<Long>();
|
||||
while (updatesCursor.moveToNext()) {
|
||||
Long mailboxId = updatesCursor.getLong(0);
|
||||
if (!mailboxesToUpdate.contains(mailboxId)) {
|
||||
mailboxesToUpdate.add(mailboxId);
|
||||
}
|
||||
if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD)) {
|
||||
Log.d(TAG, "Upload sync request for " + acct.mDisplayName);
|
||||
// See if any boxes have mail...
|
||||
Cursor updatesCursor = provider.query(Message.UPDATED_CONTENT_URI,
|
||||
new String[] {Message.MAILBOX_KEY},
|
||||
Message.ACCOUNT_KEY + "=?",
|
||||
new String[] {Long.toString(acct.mId)},
|
||||
null);
|
||||
if ((updatesCursor == null) || (updatesCursor.getCount() == 0)) return;
|
||||
ArrayList<Long> mailboxesToUpdate = new ArrayList<Long>();
|
||||
while (updatesCursor.moveToNext()) {
|
||||
Long mailboxId = updatesCursor.getLong(0);
|
||||
if (!mailboxesToUpdate.contains(mailboxId)) {
|
||||
mailboxesToUpdate.add(mailboxId);
|
||||
}
|
||||
for (long mailboxId: mailboxesToUpdate) {
|
||||
sync(context, mailboxId, syncResult);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Sync request for " + acct.mDisplayName);
|
||||
Log.d(TAG, extras.toString());
|
||||
long mailboxId = extras.getLong("MAILBOX_ID", Mailbox.NO_MAILBOX);
|
||||
boolean isInbox = false;
|
||||
if (mailboxId == Mailbox.NO_MAILBOX) {
|
||||
mailboxId = Mailbox.findMailboxOfType(context, acct.mId,
|
||||
Mailbox.TYPE_INBOX);
|
||||
isInbox = true;
|
||||
}
|
||||
if (mailboxId == Mailbox.NO_MAILBOX) return;
|
||||
sync(context, mailboxId, syncResult);
|
||||
|
||||
// Convert from minutes to seconds
|
||||
int syncFrequency = acct.mSyncInterval * 60;
|
||||
// Values < 0 are for "never" or "push"; 0 is undefined
|
||||
if (syncFrequency <= 0) return;
|
||||
Bundle ex = new Bundle();
|
||||
if (!isInbox) {
|
||||
ex.putLong("MAILBOX_ID", mailboxId);
|
||||
}
|
||||
Log.d(TAG, "Setting periodic sync for " + acct.mDisplayName + ": " +
|
||||
syncFrequency + " seconds");
|
||||
ContentResolver.addPeriodicSync(account, authority, ex, syncFrequency);
|
||||
}
|
||||
for (long mailboxId: mailboxesToUpdate) {
|
||||
sync(context, mailboxId, syncResult);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Sync request for " + acct.mDisplayName);
|
||||
Log.d(TAG, extras.toString());
|
||||
long mailboxId = extras.getLong("MAILBOX_ID", Mailbox.NO_MAILBOX);
|
||||
boolean isInbox = false;
|
||||
if (mailboxId == Mailbox.NO_MAILBOX) {
|
||||
mailboxId = Mailbox.findMailboxOfType(context, acct.mId,
|
||||
Mailbox.TYPE_INBOX);
|
||||
if (mailboxId == Mailbox.NO_MAILBOX) {
|
||||
// Update folders?
|
||||
EmailServiceProxy service =
|
||||
EmailServiceUtils.getServiceForAccount(context, null, acct.mId);
|
||||
service.updateFolderList(acct.mId);
|
||||
}
|
||||
isInbox = true;
|
||||
}
|
||||
if (mailboxId == Mailbox.NO_MAILBOX) return;
|
||||
sync(context, mailboxId, syncResult);
|
||||
|
||||
// Convert from minutes to seconds
|
||||
int syncFrequency = acct.mSyncInterval * 60;
|
||||
// Values < 0 are for "never" or "push"; 0 is undefined
|
||||
if (syncFrequency <= 0) return;
|
||||
Bundle ex = new Bundle();
|
||||
if (!isInbox) {
|
||||
ex.putLong("MAILBOX_ID", mailboxId);
|
||||
}
|
||||
Log.d(TAG, "Setting periodic sync for " + acct.mDisplayName + ": " +
|
||||
syncFrequency + " seconds");
|
||||
ContentResolver.addPeriodicSync(account, authority, ex, syncFrequency);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -64,12 +64,6 @@ public class ControllerProviderOpsTests extends ProviderTestCase2<EmailProvider>
|
|||
ContentCache.invalidateAllCaches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
mTestController.cleanupForTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight subclass of the Controller class allows injection of mock context
|
||||
*/
|
||||
|
@ -488,44 +482,6 @@ public class ControllerProviderOpsTests extends ProviderTestCase2<EmailProvider>
|
|||
return hostAuth;
|
||||
}
|
||||
|
||||
public void testIsMessagingController() {
|
||||
Account account1 = ProviderTestUtils.setupAccount("account1", false,
|
||||
mProviderContext);
|
||||
account1.mHostAuthRecv = setupSimpleHostAuth("eas");
|
||||
account1.save(mProviderContext);
|
||||
assertFalse(mTestController.isMessagingController(account1));
|
||||
Account account2 = ProviderTestUtils.setupAccount("account2", false,
|
||||
mProviderContext);
|
||||
account2.mHostAuthRecv = setupSimpleHostAuth("imap");
|
||||
account2.save(mProviderContext);
|
||||
assertTrue(mTestController.isMessagingController(account2));
|
||||
Account account3 = ProviderTestUtils.setupAccount("account3", false,
|
||||
mProviderContext);
|
||||
account3.mHostAuthRecv = setupSimpleHostAuth("pop3");
|
||||
account3.save(mProviderContext);
|
||||
assertTrue(mTestController.isMessagingController(account3));
|
||||
Account account4 = ProviderTestUtils.setupAccount("account4", false,
|
||||
mProviderContext);
|
||||
account4.mHostAuthRecv = setupSimpleHostAuth("smtp");
|
||||
account4.save(mProviderContext);
|
||||
assertFalse(mTestController.isMessagingController(account4));
|
||||
// There should be values for all of these accounts in the legacy map
|
||||
assertNotNull(mTestController.mLegacyControllerMap.get(account1.mId));
|
||||
assertNotNull(mTestController.mLegacyControllerMap.get(account2.mId));
|
||||
assertNotNull(mTestController.mLegacyControllerMap.get(account3.mId));
|
||||
assertNotNull(mTestController.mLegacyControllerMap.get(account4.mId));
|
||||
// The map should have the expected values
|
||||
assertFalse(mTestController.mLegacyControllerMap.get(account1.mId));
|
||||
assertTrue(mTestController.mLegacyControllerMap.get(account2.mId));
|
||||
assertTrue(mTestController.mLegacyControllerMap.get(account3.mId));
|
||||
assertFalse(mTestController.mLegacyControllerMap.get(account4.mId));
|
||||
// This second pass should pull values from the cache
|
||||
assertFalse(mTestController.isMessagingController(account1));
|
||||
assertTrue(mTestController.isMessagingController(account2));
|
||||
assertTrue(mTestController.isMessagingController(account3));
|
||||
assertFalse(mTestController.isMessagingController(account4));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: releasing associated data (e.g. attachments, embedded images)
|
||||
*/
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.emailcommon.mail.MockFolder;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.net.Uri;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
/**
|
||||
* This is a series of unit tests for the MessagingController class.
|
||||
*
|
||||
* Technically these are functional because they use the underlying provider framework.
|
||||
*/
|
||||
@SmallTest
|
||||
public class MessagingControllerUnitTests extends AndroidTestCase {
|
||||
|
||||
private long mAccountId;
|
||||
private Account mAccount;
|
||||
|
||||
/**
|
||||
* Delete any dummy accounts we set up for this test
|
||||
*/
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
if (mAccount != null) {
|
||||
Uri uri = ContentUris.withAppendedId(
|
||||
Account.CONTENT_URI, mAccountId);
|
||||
getContext().getContentResolver().delete(uri, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MockFolder allows setting and retrieving role & name
|
||||
*/
|
||||
private static class MyMockFolder extends MockFolder {
|
||||
private FolderRole mRole;
|
||||
private String mName;
|
||||
|
||||
public MyMockFolder(FolderRole role, String name) {
|
||||
mRole = role;
|
||||
mName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FolderRole getRole() {
|
||||
return mRole;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dummy account with minimal fields
|
||||
*/
|
||||
private void createTestAccount() {
|
||||
mAccount = new Account();
|
||||
mAccount.save(getContext());
|
||||
|
||||
mAccountId = mAccount.mId;
|
||||
}
|
||||
|
||||
}
|
|
@ -62,12 +62,6 @@ public class RefreshManagerTest extends InstrumentationTestCase {
|
|||
mTarget.registerListener(mListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
mController.cleanupForTest();
|
||||
}
|
||||
|
||||
public void testRegisterUnregisterListener() {
|
||||
// mListener is already registered
|
||||
assertEquals(1, mTarget.getListenersForTest().size());
|
||||
|
|
|
@ -77,7 +77,6 @@ public class MailboxFinderTest extends InstrumentationTestCase {
|
|||
// MailboxFinder should unregister its listener when closed.
|
||||
checkControllerResultRemoved(mMockController);
|
||||
}
|
||||
mMockController.cleanupForTest();
|
||||
Controller.injectMockControllerForTest(null);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import com.android.email.EmailConnectivityManager;
|
|||
import com.android.email.provider.ProviderTestUtils;
|
||||
import com.android.email.service.AttachmentDownloadService.DownloadRequest;
|
||||
import com.android.email.service.AttachmentDownloadService.DownloadSet;
|
||||
import com.android.email.service.EmailServiceUtils.NullEmailService;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent.Attachment;
|
||||
import com.android.emailcommon.provider.EmailContent.Message;
|
||||
|
@ -71,7 +70,7 @@ public class AttachmentDownloadServiceTests extends AccountTestCase {
|
|||
mService = new AttachmentDownloadService();
|
||||
mService.mContext = mMockContext;
|
||||
mService.addServiceIntentForTest(mAccountId, new Intent(mContext,
|
||||
NullEmailService.class));
|
||||
EmailServiceStub.class));
|
||||
mAccountManagerStub = new AttachmentDownloadService.AccountManagerStub(null);
|
||||
mService.mAccountManagerStub = mAccountManagerStub;
|
||||
mService.mConnectivityManager = new MockConnectivityManager(mContext, "mock");
|
||||
|
@ -183,10 +182,12 @@ public class AttachmentDownloadServiceTests extends AccountTestCase {
|
|||
mUsableSpace = usable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalSpace() {
|
||||
return mTotalSpace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUsableSpace() {
|
||||
return mUsableSpace;
|
||||
}
|
||||
|
@ -195,6 +196,7 @@ public class AttachmentDownloadServiceTests extends AccountTestCase {
|
|||
mMockFile.mLength = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles() {
|
||||
return mFiles;
|
||||
}
|
||||
|
@ -211,6 +213,7 @@ public class AttachmentDownloadServiceTests extends AccountTestCase {
|
|||
super("_mock");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return mLength;
|
||||
}
|
||||
|
|
|
@ -1,331 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email.service;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import com.android.email.AccountTestCase;
|
||||
import com.android.email.Controller;
|
||||
import com.android.email.provider.AccountReconciler;
|
||||
import com.android.email.provider.EmailProvider;
|
||||
import com.android.email.provider.ProviderTestUtils;
|
||||
import com.android.email.service.MailService.AccountSyncReport;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests of the Email provider.
|
||||
*
|
||||
* You can run this entire test case with:
|
||||
* runtest -c com.android.email.service.MailServiceTests email
|
||||
*/
|
||||
public class MailServiceTests extends AccountTestCase {
|
||||
|
||||
EmailProvider mProvider;
|
||||
Context mMockContext;
|
||||
|
||||
public MailServiceTests() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
pm.setComponentEnabledSetting(
|
||||
new ComponentName(getContext(), EasTestAuthenticatorService.class),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
mMockContext = getMockContext();
|
||||
// Delete any test accounts we might have created earlier
|
||||
deleteTemporaryAccountManagerAccounts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
// Delete any test accounts we might have created earlier
|
||||
deleteTemporaryAccountManagerAccounts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that the test below is functional (and non-destructive) when there are
|
||||
* prexisting (non-test) accounts in the account manager.
|
||||
*/
|
||||
public void testTestReconcileAccounts() {
|
||||
Account firstAccount = null;
|
||||
final String TEST_USER_ACCOUNT = "__user_account_test_1";
|
||||
Context context = getContext();
|
||||
try {
|
||||
// Note: Unlike calls to setupProviderAndAccountManagerAccount(), we are creating
|
||||
// *real* accounts here (not in the mock provider)
|
||||
createAccountManagerAccount(TEST_USER_ACCOUNT + TEST_ACCOUNT_SUFFIX);
|
||||
firstAccount = ProviderTestUtils.setupAccount(TEST_USER_ACCOUNT, true, context);
|
||||
// Now run the test with the "user" accounts in place
|
||||
testReconcileAccounts();
|
||||
} finally {
|
||||
if (firstAccount != null) {
|
||||
boolean firstAccountFound = false;
|
||||
// delete the provider account
|
||||
context.getContentResolver().delete(firstAccount.getUri(), null, null);
|
||||
// delete the account manager account
|
||||
android.accounts.Account[] accountManagerAccounts = AccountManager.get(context)
|
||||
.getAccountsByType(TEST_ACCOUNT_TYPE);
|
||||
for (android.accounts.Account accountManagerAccount: accountManagerAccounts) {
|
||||
if ((TEST_USER_ACCOUNT + TEST_ACCOUNT_SUFFIX)
|
||||
.equals(accountManagerAccount.name)) {
|
||||
deleteAccountManagerAccount(accountManagerAccount);
|
||||
firstAccountFound = true;
|
||||
}
|
||||
}
|
||||
assertTrue(firstAccountFound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note, there is some inherent risk in this test, as it creates *real* accounts in the
|
||||
* system (it cannot use the mock context with the Account Manager).
|
||||
*/
|
||||
public void testReconcileAccounts() {
|
||||
// Note that we can't use mMockContext for AccountManager interactions, as it isn't a fully
|
||||
// functional Context.
|
||||
Context context = getContext();
|
||||
|
||||
// Capture the baseline (account manager accounts) so we can measure the changes
|
||||
// we're making, irrespective of the number of actual accounts, and not destroy them
|
||||
android.accounts.Account[] baselineAccounts =
|
||||
AccountManager.get(context).getAccountsByType(TEST_ACCOUNT_TYPE);
|
||||
|
||||
// Set up three accounts, both in AccountManager and in EmailProvider
|
||||
Account firstAccount = setupProviderAndAccountManagerAccount(getTestAccountName("1"));
|
||||
setupProviderAndAccountManagerAccount(getTestAccountName("2"));
|
||||
setupProviderAndAccountManagerAccount(getTestAccountName("3"));
|
||||
|
||||
// Check that they're set up properly
|
||||
assertEquals(3, EmailContent.count(mMockContext, Account.CONTENT_URI, null, null));
|
||||
android.accounts.Account[] accountManagerAccounts =
|
||||
getAccountManagerAccounts(baselineAccounts);
|
||||
assertEquals(3, accountManagerAccounts.length);
|
||||
|
||||
// Delete account "2" from AccountManager
|
||||
android.accounts.Account removedAccount =
|
||||
makeAccountManagerAccount(getTestAccountEmailAddress("2"));
|
||||
deleteAccountManagerAccount(removedAccount);
|
||||
|
||||
// Confirm it's deleted
|
||||
accountManagerAccounts = getAccountManagerAccounts(baselineAccounts);
|
||||
assertEquals(2, accountManagerAccounts.length);
|
||||
|
||||
// Run the reconciler
|
||||
ContentResolver resolver = mMockContext.getContentResolver();
|
||||
MailService.reconcileAccountsWithAccountManager(context,
|
||||
makeExchangeServiceAccountList(), accountManagerAccounts, mMockContext);
|
||||
|
||||
// There should now be only two EmailProvider accounts
|
||||
assertEquals(2, EmailContent.count(mMockContext, Account.CONTENT_URI, null, null));
|
||||
|
||||
// Ok, now we've got two of each; let's delete a provider account
|
||||
resolver.delete(ContentUris.withAppendedId(Account.CONTENT_URI, firstAccount.mId),
|
||||
null, null);
|
||||
// ...and then there was one
|
||||
assertEquals(1, EmailContent.count(mMockContext, Account.CONTENT_URI, null, null));
|
||||
|
||||
// Run the reconciler
|
||||
MailService.reconcileAccountsWithAccountManager(context,
|
||||
makeExchangeServiceAccountList(), accountManagerAccounts, mMockContext);
|
||||
|
||||
// There should now be only one AccountManager account
|
||||
accountManagerAccounts = getAccountManagerAccounts(baselineAccounts);
|
||||
assertEquals(1, accountManagerAccounts.length);
|
||||
// ... and it should be account "3"
|
||||
assertEquals(getTestAccountEmailAddress("3"), accountManagerAccounts[0].name);
|
||||
}
|
||||
|
||||
public void testReconcileDetection() {
|
||||
Context context = getContext();
|
||||
List<Account> providerAccounts;
|
||||
android.accounts.Account[] accountManagerAccounts;
|
||||
|
||||
android.accounts.Account[] baselineAccounts =
|
||||
AccountManager.get(context).getAccountsByType(TEST_ACCOUNT_TYPE);
|
||||
|
||||
// Empty lists match.
|
||||
providerAccounts = new ArrayList<Account>();
|
||||
accountManagerAccounts = new android.accounts.Account[0];
|
||||
assertFalse(AccountReconciler.accountsNeedReconciling(
|
||||
context, providerAccounts, accountManagerAccounts));
|
||||
|
||||
setupProviderAndAccountManagerAccount(getTestAccountName("1"));
|
||||
accountManagerAccounts = getAccountManagerAccounts(baselineAccounts);
|
||||
providerAccounts = makeExchangeServiceAccountList();
|
||||
|
||||
// A single account, but empty list on the other side is detected as needing reconciliation
|
||||
assertTrue(AccountReconciler.accountsNeedReconciling(
|
||||
context, new ArrayList<Account>(), accountManagerAccounts));
|
||||
assertTrue(AccountReconciler.accountsNeedReconciling(
|
||||
context, providerAccounts, new android.accounts.Account[0]));
|
||||
|
||||
// Note that no reconciliation should have happened though - we just wanted to detect it.
|
||||
assertEquals(1, makeExchangeServiceAccountList().size());
|
||||
assertEquals(1, getAccountManagerAccounts(baselineAccounts).length);
|
||||
|
||||
// Single account matches - no reconciliation should be detected.
|
||||
assertFalse(AccountReconciler.accountsNeedReconciling(
|
||||
context, providerAccounts, accountManagerAccounts));
|
||||
|
||||
// Provider: 1,2,3. AccountManager: 1, 3.
|
||||
String username = getTestAccountName("2");
|
||||
ProviderTestUtils.setupAccount(getTestAccountName("2"), true, getMockContext());
|
||||
setupProviderAndAccountManagerAccount(getTestAccountName("3"));
|
||||
|
||||
accountManagerAccounts = getAccountManagerAccounts(baselineAccounts);
|
||||
providerAccounts = makeExchangeServiceAccountList();
|
||||
assertTrue(AccountReconciler.accountsNeedReconciling(
|
||||
context, providerAccounts, accountManagerAccounts));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lightweight subclass of the Controller class allows injection of mock context
|
||||
*/
|
||||
public static class TestController extends Controller {
|
||||
|
||||
protected TestController(Context providerContext, Context systemContext) {
|
||||
super(systemContext);
|
||||
setProviderContext(providerContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simple HostAuth with protocol
|
||||
*/
|
||||
private HostAuth setupSimpleHostAuth(String protocol) {
|
||||
HostAuth hostAuth = new HostAuth();
|
||||
hostAuth.mProtocol = protocol;
|
||||
return hostAuth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial testing on setupSyncReportsLocked, making sure that EAS accounts aren't scheduled
|
||||
*/
|
||||
public void testSetupSyncReportsLocked() {
|
||||
// TODO Test other functionality within setupSyncReportsLocked
|
||||
// Setup accounts of each type, all with manual sync at different intervals
|
||||
Account easAccount = ProviderTestUtils.setupAccount("account1", false, mMockContext);
|
||||
easAccount.mHostAuthRecv = setupSimpleHostAuth("eas");
|
||||
easAccount.mHostAuthSend = easAccount.mHostAuthRecv;
|
||||
easAccount.mSyncInterval = 30;
|
||||
easAccount.save(mMockContext);
|
||||
Account imapAccount = ProviderTestUtils.setupAccount("account2", false, mMockContext);
|
||||
imapAccount.mHostAuthRecv = setupSimpleHostAuth("imap");
|
||||
imapAccount.mHostAuthSend = setupSimpleHostAuth("smtp");
|
||||
imapAccount.mSyncInterval = 60;
|
||||
imapAccount.save(mMockContext);
|
||||
Account pop3Account = ProviderTestUtils.setupAccount("account3", false, mMockContext);
|
||||
pop3Account.mHostAuthRecv = setupSimpleHostAuth("pop3");
|
||||
pop3Account.mHostAuthSend = setupSimpleHostAuth("smtp");
|
||||
pop3Account.mSyncInterval = 90;
|
||||
pop3Account.save(mMockContext);
|
||||
|
||||
// Setup the SyncReport's for these Accounts
|
||||
MailService mailService = new MailService();
|
||||
mailService.mController = new TestController(mMockContext, getContext());
|
||||
try {
|
||||
mailService.setupSyncReportsLocked(MailService.SYNC_REPORTS_RESET, mMockContext);
|
||||
|
||||
// Get back the map created by MailService
|
||||
HashMap<Long, AccountSyncReport> syncReportMap = MailService.mSyncReports;
|
||||
synchronized (syncReportMap) {
|
||||
// Check the SyncReport's for correctness of sync interval
|
||||
AccountSyncReport syncReport = syncReportMap.get(easAccount.mId);
|
||||
assertNotNull(syncReport);
|
||||
// EAS sync interval should have been changed to "never"
|
||||
assertEquals(Account.CHECK_INTERVAL_NEVER, syncReport.syncInterval);
|
||||
syncReport = syncReportMap.get(imapAccount.mId);
|
||||
assertNotNull(syncReport);
|
||||
assertEquals(60, syncReport.syncInterval);
|
||||
syncReport = syncReportMap.get(pop3Account.mId);
|
||||
assertNotNull(syncReport);
|
||||
assertEquals(90, syncReport.syncInterval);
|
||||
// Change the EAS account to push
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(Account.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH);
|
||||
easAccount.update(mMockContext, cv);
|
||||
syncReportMap.clear();
|
||||
mailService.setupSyncReportsLocked(easAccount.mId, mMockContext);
|
||||
syncReport = syncReportMap.get(easAccount.mId);
|
||||
assertNotNull(syncReport);
|
||||
// EAS sync interval should be "never" in this case as well
|
||||
assertEquals(Account.CHECK_INTERVAL_NEVER, syncReport.syncInterval);
|
||||
}
|
||||
} finally {
|
||||
mailService.mController.cleanupForTest();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that setupSyncReports will skip over poorly-formed accounts which can be left
|
||||
* over after unit tests.
|
||||
*/
|
||||
public void testSetupSyncReportsWithBadAccounts() {
|
||||
// Setup accounts that trigger each skip-over case
|
||||
// 1: no email address
|
||||
Account account1 = ProviderTestUtils.setupAccount("account1", false, mMockContext);
|
||||
account1.mHostAuthRecv = setupSimpleHostAuth("imap");
|
||||
account1.mHostAuthSend = setupSimpleHostAuth("smtp");
|
||||
account1.mSyncInterval = 30;
|
||||
account1.mEmailAddress = null;
|
||||
account1.save(mMockContext);
|
||||
// 2: no receiver hostauth
|
||||
Account account2 = ProviderTestUtils.setupAccount("account2", false, mMockContext);
|
||||
account2.mHostAuthRecv = null;
|
||||
account2.mHostAuthSend = setupSimpleHostAuth("smtp");
|
||||
account2.mSyncInterval = 30;
|
||||
account2.save(mMockContext);
|
||||
// 3: no sender hostauth
|
||||
Account account3 = ProviderTestUtils.setupAccount("account3", false, mMockContext);
|
||||
account3.mHostAuthRecv = setupSimpleHostAuth("imap");
|
||||
account3.mHostAuthSend = null;
|
||||
account3.mSyncInterval = 30;
|
||||
account3.save(mMockContext);
|
||||
|
||||
// Setup the SyncReport's for these Accounts
|
||||
MailService mailService = new MailService();
|
||||
mailService.mController = new TestController(mMockContext, getContext());
|
||||
try {
|
||||
mailService.setupSyncReportsLocked(MailService.SYNC_REPORTS_RESET, mMockContext);
|
||||
// Get back the map created by MailService - it should be empty
|
||||
HashMap<Long, AccountSyncReport> syncReportMap = MailService.mSyncReports;
|
||||
assertEquals(0, syncReportMap.size());
|
||||
} finally {
|
||||
mailService.mController.cleanupForTest();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue