Start of IMAP conversion to Service architecture

* Handle startSync and loadMore
* Use SyncManager rather than MailService for periodic sync
  and upload sync
* First of many CL's to disentangle sync from UI
* Note that the large majority of this CL is a refactoring
  of IMAP specific code out of MessagingController and into
  ImapService; MessagingController will eventually be
  removed entirely from the app, as will much of Controller

Change-Id: I13546d0694479b33cf93c25920dedc1d38227f6c
This commit is contained in:
Marc Blank 2012-02-08 16:29:08 -08:00
parent 80535ef38c
commit c84467afe1
12 changed files with 2023 additions and 919 deletions

View File

@ -395,6 +395,17 @@
</intent-filter> </intent-filter>
</service> </service>
<service
android:name=".service.ImapService"
android:enabled="true"
android:permission="com.android.email.permission.ACCESS_PROVIDER"
>
<intent-filter>
<action
android:name="com.android.email.IMAP_INTENT" />
</intent-filter>
</service>
<!--Required stanza to register the EasAuthenticatorService with AccountManager --> <!--Required stanza to register the EasAuthenticatorService with AccountManager -->
<service <service
android:name=".service.EasAuthenticatorService" android:name=".service.EasAuthenticatorService"

View File

@ -16,12 +16,6 @@
package com.android.emailcommon.service; package com.android.emailcommon.service;
import com.android.emailcommon.Api;
import com.android.emailcommon.Device;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Policy;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@ -29,6 +23,12 @@ import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import com.android.emailcommon.Api;
import com.android.emailcommon.Device;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Policy;
import java.io.IOException; import java.io.IOException;
/** /**
@ -51,6 +51,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
// Private intent that will be used to connect to an independent Exchange service // Private intent that will be used to connect to an independent Exchange service
public static final String EXCHANGE_INTENT = "com.android.email.EXCHANGE_INTENT"; public static final String EXCHANGE_INTENT = "com.android.email.EXCHANGE_INTENT";
public static final String IMAP_INTENT = "com.android.email.IMAP_INTENT";
public static final String AUTO_DISCOVER_BUNDLE_ERROR_CODE = "autodiscover_error_code"; public static final String AUTO_DISCOVER_BUNDLE_ERROR_CODE = "autodiscover_error_code";
public static final String AUTO_DISCOVER_BUNDLE_HOST_AUTH = "autodiscover_host_auth"; public static final String AUTO_DISCOVER_BUNDLE_HOST_AUTH = "autodiscover_host_auth";
@ -64,6 +65,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
private final IEmailServiceCallback mCallback; private final IEmailServiceCallback mCallback;
private Object mReturn = null; private Object mReturn = null;
private IEmailService mService; private IEmailService mService;
private final boolean isRemote;
// Standard debugging // Standard debugging
public static final int DEBUG_BIT = 1; public static final int DEBUG_BIT = 1;
@ -82,6 +84,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
public EmailServiceProxy(Context _context, Class<?> _class, IEmailServiceCallback _callback) { public EmailServiceProxy(Context _context, Class<?> _class, IEmailServiceCallback _callback) {
super(_context, new Intent(_context, _class)); super(_context, new Intent(_context, _class));
mCallback = _callback; mCallback = _callback;
isRemote = false;
} }
// The following two constructors are used with remote services that must be referenced by // The following two constructors are used with remote services that must be referenced by
@ -93,6 +96,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
} catch (IOException e) { } catch (IOException e) {
} }
mCallback = _callback; mCallback = _callback;
isRemote = true;
} }
public EmailServiceProxy(Context _context, String _action, IEmailServiceCallback _callback) { public EmailServiceProxy(Context _context, String _action, IEmailServiceCallback _callback) {
@ -102,6 +106,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
} catch (IOException e) { } catch (IOException e) {
} }
mCallback = _callback; mCallback = _callback;
isRemote = true;
} }
@Override @Override
@ -109,6 +114,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
mService = IEmailService.Stub.asInterface(binder); mService = IEmailService.Stub.asInterface(binder);
} }
public boolean isRemote() {
return isRemote;
}
@Override @Override
public int getApiLevel() { public int getApiLevel() {
return Api.LEVEL; return Api.LEVEL;
@ -124,9 +133,11 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* @param background whether or not this request corresponds to a background action (i.e. * @param background whether or not this request corresponds to a background action (i.e.
* prefetch) vs a foreground action (user request) * prefetch) vs a foreground action (user request)
*/ */
@Override
public void loadAttachment(final long attachmentId, final boolean background) public void loadAttachment(final long attachmentId, final boolean background)
throws RemoteException { throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException { public void run() throws RemoteException {
try { try {
if (mCallback != null) mService.setCallback(mCallback); if (mCallback != null) mService.setCallback(mCallback);
@ -153,8 +164,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* @param mailboxId the id of the mailbox record * @param mailboxId the id of the mailbox record
* @param userRequest whether or not the user specifically asked for the sync * @param userRequest whether or not the user specifically asked for the sync
*/ */
@Override
public void startSync(final long mailboxId, final boolean userRequest) throws RemoteException { public void startSync(final long mailboxId, final boolean userRequest) throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException { public void run() throws RemoteException {
if (mCallback != null) mService.setCallback(mCallback); if (mCallback != null) mService.setCallback(mCallback);
mService.startSync(mailboxId, userRequest); mService.startSync(mailboxId, userRequest);
@ -170,8 +183,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* @param mailboxId the id of the mailbox record * @param mailboxId the id of the mailbox record
* @param userRequest whether or not the user specifically asked for the sync * @param userRequest whether or not the user specifically asked for the sync
*/ */
@Override
public void stopSync(final long mailboxId) throws RemoteException { public void stopSync(final long mailboxId) throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException { public void run() throws RemoteException {
if (mCallback != null) mService.setCallback(mCallback); if (mCallback != null) mService.setCallback(mCallback);
mService.stopSync(mailboxId); mService.stopSync(mailboxId);
@ -189,8 +204,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* @param hostAuth the hostauth object to validate * @param hostAuth the hostauth object to validate
* @return a Bundle as described above * @return a Bundle as described above
*/ */
@Override
public Bundle validate(final HostAuth hostAuth) throws RemoteException { public Bundle validate(final HostAuth hostAuth) throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException{ public void run() throws RemoteException{
if (mCallback != null) mService.setCallback(mCallback); if (mCallback != null) mService.setCallback(mCallback);
mReturn = mService.validate(hostAuth); mReturn = mService.validate(hostAuth);
@ -219,9 +236,11 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* @param password the user's password * @param password the user's password
* @return a Bundle as described above * @return a Bundle as described above
*/ */
@Override
public Bundle autoDiscover(final String userName, final String password) public Bundle autoDiscover(final String userName, final String password)
throws RemoteException { throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException{ public void run() throws RemoteException{
if (mCallback != null) mService.setCallback(mCallback); if (mCallback != null) mService.setCallback(mCallback);
mReturn = mService.autoDiscover(userName, password); mReturn = mService.autoDiscover(userName, password);
@ -244,8 +263,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* *
* @param accoundId the id of the account whose folder list is to be updated * @param accoundId the id of the account whose folder list is to be updated
*/ */
@Override
public void updateFolderList(final long accountId) throws RemoteException { public void updateFolderList(final long accountId) throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException { public void run() throws RemoteException {
if (mCallback != null) mService.setCallback(mCallback); if (mCallback != null) mService.setCallback(mCallback);
mService.updateFolderList(accountId); mService.updateFolderList(accountId);
@ -259,8 +280,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* *
* @param flags an integer whose bits represent logging flags as defined in DEBUG_* flags above * @param flags an integer whose bits represent logging flags as defined in DEBUG_* flags above
*/ */
@Override
public void setLogging(final int flags) throws RemoteException { public void setLogging(final int flags) throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException { public void run() throws RemoteException {
if (mCallback != null) mService.setCallback(mCallback); if (mCallback != null) mService.setCallback(mCallback);
mService.setLogging(flags); mService.setLogging(flags);
@ -274,8 +297,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* *
* @param cb a callback object through which all service callbacks are executed * @param cb a callback object through which all service callbacks are executed
*/ */
@Override
public void setCallback(final IEmailServiceCallback cb) throws RemoteException { public void setCallback(final IEmailServiceCallback cb) throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException { public void run() throws RemoteException {
mService.setCallback(cb); mService.setCallback(cb);
} }
@ -289,8 +314,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* *
* @param accountId the id of the account whose host information has changed * @param accountId the id of the account whose host information has changed
*/ */
@Override
public void hostChanged(final long accountId) throws RemoteException { public void hostChanged(final long accountId) throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException { public void run() throws RemoteException {
mService.hostChanged(accountId); mService.hostChanged(accountId);
} }
@ -303,9 +330,11 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* @param messageId the id of the message containing the meeting request * @param messageId the id of the message containing the meeting request
* @param response the response code, as defined in EmailServiceConstants * @param response the response code, as defined in EmailServiceConstants
*/ */
@Override
public void sendMeetingResponse(final long messageId, final int response) public void sendMeetingResponse(final long messageId, final int response)
throws RemoteException { throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException { public void run() throws RemoteException {
if (mCallback != null) mService.setCallback(mCallback); if (mCallback != null) mService.setCallback(mCallback);
mService.sendMeetingResponse(messageId, response); mService.sendMeetingResponse(messageId, response);
@ -314,11 +343,19 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
} }
/** /**
* Not yet used; intended to request the sync adapter to load a complete message * Request the sync adapter to load a complete message
* *
* @param messageId the id of the message to be loaded * @param messageId the id of the message to be loaded
*/ */
public void loadMore(long messageId) throws RemoteException { @Override
public void loadMore(final long messageId) throws RemoteException {
setTask(new ProxyTask() {
@Override
public void run() throws RemoteException {
if (mCallback != null) mService.setCallback(mCallback);
mService.loadMore(messageId);
}
}, "startSync");
} }
/** /**
@ -327,6 +364,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* @param accountId the account in which the folder is to be created * @param accountId the account in which the folder is to be created
* @param name the name of the folder to be created * @param name the name of the folder to be created
*/ */
@Override
public boolean createFolder(long accountId, String name) throws RemoteException { public boolean createFolder(long accountId, String name) throws RemoteException {
return false; return false;
} }
@ -337,6 +375,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* @param accountId the account in which the folder resides * @param accountId the account in which the folder resides
* @param name the name of the folder to be deleted * @param name the name of the folder to be deleted
*/ */
@Override
public boolean deleteFolder(long accountId, String name) throws RemoteException { public boolean deleteFolder(long accountId, String name) throws RemoteException {
return false; return false;
} }
@ -348,6 +387,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* @param oldName the name of the existing folder * @param oldName the name of the existing folder
* @param newName the new name for the folder * @param newName the new name for the folder
*/ */
@Override
public boolean renameFolder(long accountId, String oldName, String newName) public boolean renameFolder(long accountId, String oldName, String newName)
throws RemoteException { throws RemoteException {
return false; return false;
@ -361,8 +401,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* *
* @param accountId the account whose data is to be deleted * @param accountId the account whose data is to be deleted
*/ */
@Override
public void deleteAccountPIMData(final long accountId) throws RemoteException { public void deleteAccountPIMData(final long accountId) throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException { public void run() throws RemoteException {
mService.deleteAccountPIMData(accountId); mService.deleteAccountPIMData(accountId);
} }
@ -385,9 +427,11 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
* @param destMailboxId the id of the mailbox into which search results are appended * @param destMailboxId the id of the mailbox into which search results are appended
* @return the total number of matches for this search (regardless of how many were requested) * @return the total number of matches for this search (regardless of how many were requested)
*/ */
@Override
public int searchMessages(final long accountId, final SearchParams searchParams, public int searchMessages(final long accountId, final SearchParams searchParams,
final long destMailboxId) throws RemoteException { final long destMailboxId) throws RemoteException {
setTask(new ProxyTask() { setTask(new ProxyTask() {
@Override
public void run() throws RemoteException{ public void run() throws RemoteException{
if (mCallback != null) mService.setCallback(mCallback); if (mCallback != null) mService.setCallback(mCallback);
mReturn = mService.searchMessages(accountId, searchParams, destMailboxId); mReturn = mService.searchMessages(accountId, searchParams, destMailboxId);
@ -400,6 +444,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
return (Integer)mReturn; return (Integer)mReturn;
} }
} }
@Override
public IBinder asBinder() { public IBinder asBinder() {
return null; return null;
} }

View File

@ -65,4 +65,12 @@ oneway interface IEmailServiceCallback {
* progress = 0 for "start", 1..100 for optional progress reports * progress = 0 for "start", 1..100 for optional progress reports
*/ */
void sendMessageStatus(long messageId, String subject, int statusCode, int progress); void sendMessageStatus(long messageId, String subject, int statusCode, int progress);
/**
* Callback to indicate that a particular message is being loaded
* messageId = the message being sent
* statusCode = 0 for OK, 1 for progress, other codes for error
* progress = 0 for "start", 1..100 for optional progress reports
*/
void loadMessageStatus(long messageId, int statusCode, int progress);
} }

View File

@ -23,5 +23,6 @@
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.android.email.provider" android:contentAuthority="com.android.email.provider"
android:accountType="com.android.email" android:accountType="com.android.email"
android:supportsUploading="false" android:supportsUploading="true"
android:allowParallelSyncs="true"
/> />

View File

@ -48,6 +48,7 @@ import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.EmailContent.MessageColumns; import com.android.emailcommon.provider.EmailContent.MessageColumns;
import com.android.emailcommon.provider.HostAuth; import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox; import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.emailcommon.service.EmailServiceStatus; import com.android.emailcommon.service.EmailServiceStatus;
import com.android.emailcommon.service.IEmailService; import com.android.emailcommon.service.IEmailService;
import com.android.emailcommon.service.IEmailServiceCallback; import com.android.emailcommon.service.IEmailServiceCallback;
@ -339,6 +340,7 @@ public class Controller {
/** /**
* Request a remote update of mailboxes for an account. * Request a remote update of mailboxes for an account.
*/ */
@SuppressWarnings("deprecation")
public void updateMailboxList(final long accountId) { public void updateMailboxList(final long accountId) {
Utility.runAsync(new Runnable() { Utility.runAsync(new Runnable() {
@Override @Override
@ -367,23 +369,15 @@ public class Controller {
* Functionally this is quite similar to updateMailbox(), but it's a separate API and * 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. * 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) { public void serviceCheckMail(final long accountId, final long mailboxId, final long tag) {
IEmailService service = getServiceForAccount(accountId); IEmailService service = getServiceForAccount(accountId);
if (service != null) { if (service != null) {
// Service implementation
// try {
// TODO this isn't quite going to work, because we're going to get the
// generic (UI) callbacks and not the ones we need to restart the ol' service.
// service.startSync(mailboxId, tag);
mLegacyListener.checkMailFinished(mContext, accountId, mailboxId, tag); mLegacyListener.checkMailFinished(mContext, accountId, mailboxId, tag);
// } 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 { } else {
// MessagingController implementation // MessagingController implementation
Utility.runAsync(new Runnable() { Utility.runAsync(new Runnable() {
@Override
public void run() { public void run() {
mLegacyController.checkMail(accountId, tag, mLegacyListener); mLegacyController.checkMail(accountId, tag, mLegacyListener);
} }
@ -398,6 +392,7 @@ public class Controller {
* a simple message list. We should also at this point queue up a background task of * 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. * 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) { public void updateMailbox(final long accountId, final long mailboxId, boolean userRequest) {
IEmailService service = getServiceForAccount(accountId); IEmailService service = getServiceForAccount(accountId);
@ -412,6 +407,7 @@ public class Controller {
} else { } else {
// MessagingController implementation // MessagingController implementation
Utility.runAsync(new Runnable() { Utility.runAsync(new Runnable() {
@Override
public void run() { public void run() {
// TODO shouldn't be passing fully-build accounts & mailboxes into APIs // TODO shouldn't be passing fully-build accounts & mailboxes into APIs
Account account = Account account =
@ -438,11 +434,13 @@ public class Controller {
* @param messageId the message to load * @param messageId the message to load
* @param callback the Controller callback by which results will be reported * @param callback the Controller callback by which results will be reported
*/ */
@SuppressWarnings("deprecation")
public void loadMessageForView(final long messageId) { public void loadMessageForView(final long messageId) {
// Split here for target type (Service or MessagingController) // Split here for target type (Service or MessagingController)
IEmailService service = getServiceForMessage(messageId); EmailServiceProxy service = getServiceForMessage(messageId);
if (service != null) { if (service != null && service.isRemote()) {
// Get rid of this!!
// There is no service implementation, so we'll just jam the value, log the error, // There is no service implementation, so we'll just jam the value, log the error,
// and get out of here. // and get out of here.
Uri uri = ContentUris.withAppendedId(Message.CONTENT_URI, messageId); Uri uri = ContentUris.withAppendedId(Message.CONTENT_URI, messageId);
@ -456,9 +454,16 @@ public class Controller {
listener.loadMessageForViewCallback(null, accountId, messageId, 100); listener.loadMessageForViewCallback(null, accountId, messageId, 100);
} }
} }
} else if (service != null) {
// IMAP here for now
try {
service.loadMore(messageId);
} catch (RemoteException e) {
}
} else { } else {
// MessagingController implementation // MessagingController implementation
Utility.runAsync(new Runnable() { Utility.runAsync(new Runnable() {
@Override
public void run() { public void run() {
mLegacyController.loadMessageForView(messageId, mLegacyListener); mLegacyController.loadMessageForView(messageId, mLegacyListener);
} }
@ -593,6 +598,7 @@ public class Controller {
sendPendingMessages(accountId); sendPendingMessages(accountId);
} }
@SuppressWarnings("deprecation")
private void sendPendingMessagesSmtp(long accountId) { private void sendPendingMessagesSmtp(long accountId) {
// for IMAP & POP only, (attempt to) send the message now // for IMAP & POP only, (attempt to) send the message now
final Account account = final Account account =
@ -602,6 +608,7 @@ public class Controller {
} }
final long sentboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_SENT); final long sentboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_SENT);
Utility.runAsync(new Runnable() { Utility.runAsync(new Runnable() {
@Override
public void run() { public void run() {
mLegacyController.sendPendingMessages(account, sentboxId, mLegacyListener); mLegacyController.sendPendingMessages(account, sentboxId, mLegacyListener);
} }
@ -644,8 +651,10 @@ public class Controller {
* look up limit * look up limit
* write limit into all mailboxes for that account * write limit into all mailboxes for that account
*/ */
@SuppressWarnings("deprecation")
public void resetVisibleLimits() { public void resetVisibleLimits() {
Utility.runAsync(new Runnable() { Utility.runAsync(new Runnable() {
@Override
public void run() { public void run() {
ContentResolver resolver = mProviderContext.getContentResolver(); ContentResolver resolver = mProviderContext.getContentResolver();
Cursor c = null; Cursor c = null;
@ -682,6 +691,7 @@ public class Controller {
*/ */
public void loadMoreMessages(final long mailboxId) { public void loadMoreMessages(final long mailboxId) {
EmailAsyncTask.runAsyncParallel(new Runnable() { EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() { public void run() {
Mailbox mailbox = Mailbox.restoreMailboxWithId(mProviderContext, mailboxId); Mailbox mailbox = Mailbox.restoreMailboxWithId(mProviderContext, mailboxId);
if (mailbox == null) { if (mailbox == null) {
@ -746,6 +756,7 @@ public class Controller {
*/ */
public void deleteMessage(final long messageId) { public void deleteMessage(final long messageId) {
EmailAsyncTask.runAsyncParallel(new Runnable() { EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() { public void run() {
deleteMessageSync(messageId); deleteMessageSync(messageId);
} }
@ -760,6 +771,7 @@ public class Controller {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
EmailAsyncTask.runAsyncParallel(new Runnable() { EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() { public void run() {
for (long messageId: messageIds) { for (long messageId: messageIds) {
deleteMessageSync(messageId); deleteMessageSync(messageId);
@ -809,10 +821,6 @@ public class Controller {
cv.put(EmailContent.MessageColumns.MAILBOX_KEY, trashMailboxId); cv.put(EmailContent.MessageColumns.MAILBOX_KEY, trashMailboxId);
resolver.update(uri, cv, null, null); resolver.update(uri, cv, null, null);
} }
if (isMessagingController(account)) {
mLegacyController.processPendingActions(account.mId);
}
} }
/** /**
@ -834,6 +842,7 @@ public class Controller {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
return EmailAsyncTask.runAsyncParallel(new Runnable() { return EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() { public void run() {
Account account = Account.getAccountForMessageId(mProviderContext, messageIds[0]); Account account = Account.getAccountForMessageId(mProviderContext, messageIds[0]);
if (account != null) { if (account != null) {
@ -845,9 +854,6 @@ public class Controller {
EmailContent.Message.SYNCED_CONTENT_URI, messageId); EmailContent.Message.SYNCED_CONTENT_URI, messageId);
resolver.update(uri, cv, null, null); resolver.update(uri, cv, null, null);
} }
if (isMessagingController(account)) {
mLegacyController.processPendingActions(account.mId);
}
} }
} }
}); });
@ -888,13 +894,6 @@ public class Controller {
private void updateMessageSync(long messageId, ContentValues cv) { private void updateMessageSync(long messageId, ContentValues cv) {
Uri uri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId); Uri uri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId);
mProviderContext.getContentResolver().update(uri, cv, null, null); mProviderContext.getContentResolver().update(uri, cv, null, null);
// Service runs automatically, MessagingController needs a kick
long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
if (accountId == Account.NO_ACCOUNT) return;
if (isMessagingController(accountId)) {
mLegacyController.processPendingActions(accountId);
}
} }
/** /**
@ -906,6 +905,7 @@ public class Controller {
public void setMessageAnsweredOrForwarded(final long messageId, public void setMessageAnsweredOrForwarded(final long messageId,
final int flag) { final int flag) {
EmailAsyncTask.runAsyncParallel(new Runnable() { EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() { public void run() {
Message msg = Message.restoreMessageWithId(mProviderContext, messageId); Message msg = Message.restoreMessageWithId(mProviderContext, messageId);
if (msg == null) { if (msg == null) {
@ -1019,7 +1019,9 @@ public class Controller {
if (Email.DEBUG) { if (Email.DEBUG) {
Log.d(Logging.LOG_TAG, "Search: " + searchParams.mFilter); Log.d(Logging.LOG_TAG, "Search: " + searchParams.mFilter);
} }
return mLegacyController.searchMailbox(accountId, searchParams, searchMailboxId); // Plumb this
return 0;
//return mLegacyController.searchMailbox(accountId, searchParams, searchMailboxId);
} }
} }
@ -1085,7 +1087,7 @@ public class Controller {
* @param messageId the message of interest * @param messageId the message of interest
* @result service proxy, or null if n/a * @result service proxy, or null if n/a
*/ */
private IEmailService getServiceForMessage(long messageId) { private EmailServiceProxy getServiceForMessage(long messageId) {
// TODO make this more efficient, caching the account, smaller lookup here, etc. // TODO make this more efficient, caching the account, smaller lookup here, etc.
Message message = Message.restoreMessageWithId(mProviderContext, messageId); Message message = Message.restoreMessageWithId(mProviderContext, messageId);
if (message == null) { if (message == null) {
@ -1100,15 +1102,22 @@ public class Controller {
* @param accountId the message of interest * @param accountId the message of interest
* @result service proxy, or null if n/a * @result service proxy, or null if n/a
*/ */
private IEmailService getServiceForAccount(long accountId) { private EmailServiceProxy getServiceForAccount(long accountId) {
if (isMessagingController(accountId)) return null; if (isMessagingController(accountId)) return null;
if (Account.getProtocol(mContext, accountId).equals(HostAuth.SCHEME_IMAP)) {
return getImapEmailService();
}
return getExchangeEmailService(); return getExchangeEmailService();
} }
private IEmailService getExchangeEmailService() { private EmailServiceProxy getExchangeEmailService() {
return EmailServiceUtils.getExchangeService(mContext, mServiceCallback); return EmailServiceUtils.getExchangeService(mContext, mServiceCallback);
} }
private EmailServiceProxy getImapEmailService() {
return EmailServiceUtils.getImapService(mContext, mServiceCallback);
}
/** /**
* Simple helper to determine if legacy MessagingController should be used * Simple helper to determine if legacy MessagingController should be used
*/ */
@ -1121,7 +1130,7 @@ public class Controller {
Boolean isLegacyController = mLegacyControllerMap.get(accountId); Boolean isLegacyController = mLegacyControllerMap.get(accountId);
if (isLegacyController == null) { if (isLegacyController == null) {
String protocol = Account.getProtocol(mProviderContext, accountId); String protocol = Account.getProtocol(mProviderContext, accountId);
isLegacyController = ("pop3".equals(protocol) || "imap".equals(protocol)); isLegacyController = (HostAuth.SCHEME_POP3.equals(protocol));
mLegacyControllerMap.put(accountId, isLegacyController); mLegacyControllerMap.put(accountId, isLegacyController);
} }
return isLegacyController; return isLegacyController;
@ -1581,8 +1590,7 @@ public class Controller {
*/ */
private class ServiceCallback extends IEmailServiceCallback.Stub { private class ServiceCallback extends IEmailServiceCallback.Stub {
private final static boolean DEBUG_FAIL_DOWNLOADS = false; // do not check in "true" @Override
public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode, public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode,
int progress) { int progress) {
MessagingException result = mapStatusToException(statusCode); MessagingException result = mapStatusToException(statusCode);
@ -1591,10 +1599,6 @@ public class Controller {
progress = 100; progress = 100;
break; break;
case EmailServiceStatus.IN_PROGRESS: case EmailServiceStatus.IN_PROGRESS:
if (DEBUG_FAIL_DOWNLOADS && progress > 75) {
result = new MessagingException(
String.valueOf(EmailServiceStatus.CONNECTION_ERROR));
}
// discard progress reports that look like sentinels // discard progress reports that look like sentinels
if (progress < 0 || progress >= 100) { if (progress < 0 || progress >= 100) {
return; return;
@ -1616,6 +1620,7 @@ public class Controller {
* However, this is sufficient for basic "progress=100" notification that message send * However, this is sufficient for basic "progress=100" notification that message send
* has just completed. * has just completed.
*/ */
@Override
public void sendMessageStatus(long messageId, String subject, int statusCode, public void sendMessageStatus(long messageId, String subject, int statusCode,
int progress) { int progress) {
long accountId = -1; // This should be in the callback long accountId = -1; // This should be in the callback
@ -1638,6 +1643,35 @@ public class Controller {
} }
} }
/**
* Note, this is an incomplete implementation of this callback, because we are
* not getting things back from Service in quite the same way as from MessagingController.
* However, this is sufficient for basic "progress=100" notification that message send
* has just completed.
*/
@Override
public void loadMessageStatus(long messageId, int statusCode, int progress) {
long accountId = -1; // This should be in the callback
MessagingException result = mapStatusToException(statusCode);
switch (statusCode) {
case EmailServiceStatus.SUCCESS:
progress = 100;
break;
case EmailServiceStatus.IN_PROGRESS:
// discard progress reports that look like sentinels
if (progress < 0 || progress >= 100) {
return;
}
break;
}
synchronized(mListeners) {
for (Result listener : mListeners) {
listener.loadMessageForViewCallback(result, accountId, messageId, progress);
}
}
}
@Override
public void syncMailboxListStatus(long accountId, int statusCode, int progress) { public void syncMailboxListStatus(long accountId, int statusCode, int progress) {
MessagingException result = mapStatusToException(statusCode); MessagingException result = mapStatusToException(statusCode);
switch (statusCode) { switch (statusCode) {
@ -1658,6 +1692,7 @@ public class Controller {
} }
} }
@Override
public void syncMailboxStatus(long mailboxId, int statusCode, int progress) { public void syncMailboxStatus(long mailboxId, int statusCode, int progress) {
MessagingException result = mapStatusToException(statusCode); MessagingException result = mapStatusToException(statusCode);
switch (statusCode) { switch (statusCode) {
@ -1752,6 +1787,7 @@ public class Controller {
} }
} }
@Override
public void loadAttachmentStatus(final long messageId, final long attachmentId, public void loadAttachmentStatus(final long messageId, final long attachmentId,
final int status, final int progress) { final int status, final int progress) {
broadcastCallback(new ServiceCallbackWrapper() { broadcastCallback(new ServiceCallbackWrapper() {
@ -1766,6 +1802,10 @@ public class Controller {
public void sendMessageStatus(long messageId, String subject, int statusCode, int progress){ public void sendMessageStatus(long messageId, String subject, int statusCode, int progress){
} }
@Override
public void loadMessageStatus(long messageId, int statusCode, int progress){
}
@Override @Override
public void syncMailboxListStatus(long accountId, int statusCode, int progress) { public void syncMailboxListStatus(long accountId, int statusCode, int progress) {
} }
@ -1782,20 +1822,25 @@ public class Controller {
*/ */
private final IEmailService.Stub mBinder = new IEmailService.Stub() { private final IEmailService.Stub mBinder = new IEmailService.Stub() {
@Override
public Bundle validate(HostAuth hostAuth) { public Bundle validate(HostAuth hostAuth) {
return null; return null;
} }
@Override
public Bundle autoDiscover(String userName, String password) { public Bundle autoDiscover(String userName, String password) {
return null; return null;
} }
@Override
public void startSync(long mailboxId, boolean userRequest) { public void startSync(long mailboxId, boolean userRequest) {
} }
@Override
public void stopSync(long mailboxId) { public void stopSync(long mailboxId) {
} }
@Override
public void loadAttachment(long attachmentId, boolean background) public void loadAttachment(long attachmentId, boolean background)
throws RemoteException { throws RemoteException {
Attachment att = Attachment.restoreAttachmentWithId(ControllerService.this, Attachment att = Attachment.restoreAttachmentWithId(ControllerService.this,
@ -1835,41 +1880,52 @@ public class Controller {
} }
} }
@Override
public void updateFolderList(long accountId) { public void updateFolderList(long accountId) {
} }
@Override
public void hostChanged(long accountId) { public void hostChanged(long accountId) {
} }
@Override
public void setLogging(int flags) { public void setLogging(int flags) {
} }
@Override
public void sendMeetingResponse(long messageId, int response) { public void sendMeetingResponse(long messageId, int response) {
} }
@Override
public void loadMore(long messageId) { public void loadMore(long messageId) {
} }
// The following three methods are not implemented in this version // The following three methods are not implemented in this version
@Override
public boolean createFolder(long accountId, String name) { public boolean createFolder(long accountId, String name) {
return false; return false;
} }
@Override
public boolean deleteFolder(long accountId, String name) { public boolean deleteFolder(long accountId, String name) {
return false; return false;
} }
@Override
public boolean renameFolder(long accountId, String oldName, String newName) { public boolean renameFolder(long accountId, String oldName, String newName) {
return false; return false;
} }
@Override
public void setCallback(IEmailServiceCallback cb) { public void setCallback(IEmailServiceCallback cb) {
sCallbackList.register(cb); sCallbackList.register(cb);
} }
@Override
public void deleteAccountPIMData(long accountId) { public void deleteAccountPIMData(long accountId) {
} }
@Override
public int searchMessages(long accountId, SearchParams searchParams, public int searchMessages(long accountId, SearchParams searchParams,
long destMailboxId) { long destMailboxId) {
return 0; return 0;

View File

@ -41,7 +41,6 @@ import com.android.emailcommon.mail.Flag;
import com.android.emailcommon.mail.Folder; import com.android.emailcommon.mail.Folder;
import com.android.emailcommon.mail.Folder.FolderType; import com.android.emailcommon.mail.Folder.FolderType;
import com.android.emailcommon.mail.Folder.MessageRetrievalListener; import com.android.emailcommon.mail.Folder.MessageRetrievalListener;
import com.android.emailcommon.mail.Folder.MessageUpdateCallbacks;
import com.android.emailcommon.mail.Folder.OpenMode; import com.android.emailcommon.mail.Folder.OpenMode;
import com.android.emailcommon.mail.Message; import com.android.emailcommon.mail.Message;
import com.android.emailcommon.mail.MessagingException; import com.android.emailcommon.mail.MessagingException;
@ -54,15 +53,12 @@ import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.EmailContent.MessageColumns; import com.android.emailcommon.provider.EmailContent.MessageColumns;
import com.android.emailcommon.provider.EmailContent.SyncColumns; import com.android.emailcommon.provider.EmailContent.SyncColumns;
import com.android.emailcommon.provider.Mailbox; import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.service.SearchParams;
import com.android.emailcommon.utility.AttachmentUtilities; import com.android.emailcommon.utility.AttachmentUtilities;
import com.android.emailcommon.utility.ConversionUtilities; import com.android.emailcommon.utility.ConversionUtilities;
import com.android.emailcommon.utility.Utility; import com.android.emailcommon.utility.Utility;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -103,23 +99,11 @@ public class MessagingController implements Runnable {
*/ */
private static final int MAX_SMALL_MESSAGE_SIZE = (25 * 1024); private static final int MAX_SMALL_MESSAGE_SIZE = (25 * 1024);
private static final Flag[] FLAG_LIST_SEEN = new Flag[] { Flag.SEEN };
private static final Flag[] FLAG_LIST_FLAGGED = new Flag[] { Flag.FLAGGED };
private static final Flag[] FLAG_LIST_ANSWERED = new Flag[] { Flag.ANSWERED };
/** /**
* We write this into the serverId field of messages that will never be upsynced. * We write this into the serverId field of messages that will never be upsynced.
*/ */
private static final String LOCAL_SERVERID_PREFIX = "Local-"; private static final String LOCAL_SERVERID_PREFIX = "Local-";
/**
* 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
* shouldn't be an issue
*/
private static final HashMap<Long, SortableMessage[]> sSearchResults =
new HashMap<Long, SortableMessage[]>();
private static final ContentValues PRUNE_ATTACHMENT_CV = new ContentValues(); private static final ContentValues PRUNE_ATTACHMENT_CV = new ContentValues();
static { static {
PRUNE_ATTACHMENT_CV.putNull(AttachmentColumns.CONTENT_URI); PRUNE_ATTACHMENT_CV.putNull(AttachmentColumns.CONTENT_URI);
@ -363,7 +347,6 @@ public class MessagingController implements Runnable {
} }
NotificationController nc = NotificationController.getInstance(mContext); NotificationController nc = NotificationController.getInstance(mContext);
try { try {
processPendingActionsSynchronous(account);
// Select generic sync or store-specific sync // Select generic sync or store-specific sync
SyncResults results = synchronizeMailboxGeneric(account, folder); SyncResults results = synchronizeMailboxGeneric(account, folder);
@ -592,135 +575,6 @@ public class MessagingController implements Runnable {
} }
/**
* A message and numeric uid that's easily sortable
*/
private static class SortableMessage {
private final Message mMessage;
private final long mUid;
SortableMessage(Message message, long uid) {
mMessage = message;
mUid = uid;
}
}
public int searchMailbox(long accountId, SearchParams searchParams, long destMailboxId)
throws MessagingException {
try {
return searchMailboxImpl(accountId, searchParams, destMailboxId);
} finally {
// Tell UI that we're done loading any search results (no harm calling this even if we
// encountered an error or never sent a "started" message)
mListeners.synchronizeMailboxFinished(accountId, destMailboxId, 0, 0, null);
}
}
private int searchMailboxImpl(long accountId, SearchParams searchParams,
final long destMailboxId) throws MessagingException {
final Account account = Account.restoreAccountWithId(mContext, accountId);
final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, searchParams.mMailboxId);
final Mailbox destMailbox = Mailbox.restoreMailboxWithId(mContext, destMailboxId);
if (account == null || mailbox == null || destMailbox == null) {
Log.d(Logging.LOG_TAG, "Attempted search for " + searchParams
+ " but account or mailbox information was missing");
return 0;
}
// Tell UI that we're loading messages
mListeners.synchronizeMailboxStarted(accountId, destMailbox.mId);
Store remoteStore = Store.getInstance(account, mContext);
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
remoteFolder.open(OpenMode.READ_WRITE);
SortableMessage[] sortableMessages = new SortableMessage[0];
if (searchParams.mOffset == 0) {
// Get the "bare" messages (basically uid)
Message[] remoteMessages = remoteFolder.getMessages(searchParams, null);
int remoteCount = remoteMessages.length;
if (remoteCount > 0) {
sortableMessages = new SortableMessage[remoteCount];
int i = 0;
for (Message msg : remoteMessages) {
sortableMessages[i++] = new SortableMessage(msg, Long.parseLong(msg.getUid()));
}
// Sort the uid's, most recent first
// Note: Not all servers will be nice and return results in the order of request;
// those that do will see messages arrive from newest to oldest
Arrays.sort(sortableMessages, new Comparator<SortableMessage>() {
@Override
public int compare(SortableMessage lhs, SortableMessage rhs) {
return lhs.mUid > rhs.mUid ? -1 : lhs.mUid < rhs.mUid ? 1 : 0;
}
});
sSearchResults.put(accountId, sortableMessages);
}
} else {
sortableMessages = sSearchResults.get(accountId);
}
final int numSearchResults = sortableMessages.length;
final int numToLoad =
Math.min(numSearchResults - searchParams.mOffset, searchParams.mLimit);
if (numToLoad <= 0) {
return 0;
}
final ArrayList<Message> messageList = new ArrayList<Message>();
for (int i = searchParams.mOffset; i < numToLoad + searchParams.mOffset; i++) {
messageList.add(sortableMessages[i].mMessage);
}
// Get everything in one pass, rather than two (as in sync); this starts getting us
// usable results quickly.
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.FLAGS);
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(FetchProfile.Item.STRUCTURE);
fp.add(FetchProfile.Item.BODY_SANE);
remoteFolder.fetch(messageList.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
EmailContent.Message localMessage = new EmailContent.Message();
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, mContext);
localMessage.mMailboxKey = destMailboxId;
// We load 50k or so; maybe it's complete, maybe not...
int flag = EmailContent.Message.FLAG_LOADED_COMPLETE;
// We store the serverId of the source mailbox into protocolSearchInfo
// This will be used by loadMessageForView, etc. to use the proper remote
// folder
localMessage.mProtocolSearchInfo = mailbox.mServerId;
if (message.getSize() > Store.FETCH_BODY_SANE_SUGGESTED_SIZE) {
flag = EmailContent.Message.FLAG_LOADED_PARTIAL;
}
copyOneMessageToProvider(message, localMessage, flag, mContext);
} 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) {
}
});
return numSearchResults;
}
/** /**
* Generic synchronizer - used for POP3 and IMAP. * Generic synchronizer - used for POP3 and IMAP.
* *
@ -1053,667 +907,6 @@ public class MessagingController implements Runnable {
} }
} }
public void processPendingActions(final long accountId) {
put("processPendingActions", null, new Runnable() {
@Override
public void run() {
try {
Account account = Account.restoreAccountWithId(mContext, accountId);
if (account == null) {
return;
}
processPendingActionsSynchronous(account);
}
catch (MessagingException me) {
if (Logging.LOGD) {
Log.v(Logging.LOG_TAG, "processPendingActions", me);
}
/*
* Ignore any exceptions from the commands. Commands will be processed
* on the next round.
*/
}
}
});
}
/**
* 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 void processPendingActionsSynchronous(Account account)
throws MessagingException {
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account));
ContentResolver resolver = mContext.getContentResolver();
String[] accountIdArgs = new String[] { Long.toString(account.mId) };
// Handle deletes first, it's always better to get rid of things first
processPendingDeletesSynchronous(account, resolver, accountIdArgs);
// Handle uploads (currently, only to sent messages)
processPendingUploadsSynchronous(account, resolver, accountIdArgs);
// Now handle updates / upsyncs
processPendingUpdatesSynchronous(account, resolver, accountIdArgs);
}
/**
* Get the mailbox corresponding to the remote location of a message; this will normally be
* the mailbox whose _id is mailboxKey, except for search results, where we must look it up
* by serverId
* @param message the message in question
* @return the mailbox in which the message resides on the server
*/
private Mailbox getRemoteMailboxForMessage(EmailContent.Message message) {
// If this is a search result, use the protocolSearchInfo field to get the server info
if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
long accountKey = message.mAccountKey;
String protocolSearchInfo = message.mProtocolSearchInfo;
if (accountKey == mLastSearchAccountKey &&
protocolSearchInfo.equals(mLastSearchServerId)) {
return mLastSearchRemoteMailbox;
}
Cursor c = mContext.getContentResolver().query(Mailbox.CONTENT_URI,
Mailbox.CONTENT_PROJECTION, Mailbox.PATH_AND_ACCOUNT_SELECTION,
new String[] {protocolSearchInfo, Long.toString(accountKey)},
null);
try {
if (c.moveToNext()) {
Mailbox mailbox = new Mailbox();
mailbox.restore(c);
mLastSearchAccountKey = accountKey;
mLastSearchServerId = protocolSearchInfo;
mLastSearchRemoteMailbox = mailbox;
return mailbox;
} else {
return null;
}
} finally {
c.close();
}
} else {
return Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
}
}
/**
* 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 void processPendingDeletesSynchronous(Account account,
ContentResolver resolver, String[] accountIdArgs) {
Cursor deletes = resolver.query(EmailContent.Message.DELETED_CONTENT_URI,
EmailContent.Message.CONTENT_PROJECTION,
EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs,
EmailContent.MessageColumns.MAILBOX_KEY);
long lastMessageId = -1;
try {
// Defer setting up the store until we know we need to access it
Store remoteStore = null;
// loop through messages marked as deleted
while (deletes.moveToNext()) {
boolean deleteFromTrash = false;
EmailContent.Message oldMessage =
EmailContent.getContent(deletes, EmailContent.Message.class);
if (oldMessage != null) {
lastMessageId = oldMessage.mId;
Mailbox mailbox = getRemoteMailboxForMessage(oldMessage);
if (mailbox == null) {
continue; // Mailbox removed. Move to the next message.
}
deleteFromTrash = mailbox.mType == Mailbox.TYPE_TRASH;
// Load the remote store if it will be needed
if (remoteStore == null && deleteFromTrash) {
remoteStore = Store.getInstance(account, mContext);
}
// Dispatch here for specific change types
if (deleteFromTrash) {
// Move message to trash
processPendingDeleteFromTrash(remoteStore, account, mailbox, oldMessage);
}
}
// Finally, delete the update
Uri uri = ContentUris.withAppendedId(EmailContent.Message.DELETED_CONTENT_URI,
oldMessage.mId);
resolver.delete(uri, null, null);
}
} catch (MessagingException me) {
// Presumably an error here is an account connection failure, so there is
// no point in continuing through the rest of the pending updates.
if (Email.DEBUG) {
Log.d(Logging.LOG_TAG, "Unable to process pending delete for id="
+ lastMessageId + ": " + me);
}
} finally {
deletes.close();
}
}
/**
* Scan for messages that are in Sent, and are in need of upload,
* and send them to the server. "In need of upload" is defined as:
* serverId == null (no UID has been assigned)
* or
* message is in the updated list
*
* Note we also look for messages that are moving from drafts->outbox->sent. They never
* go through "drafts" or "outbox" on the server, so we hang onto these until they can be
* uploaded directly to the Sent folder.
*
* @param account
* @param resolver
* @param accountIdArgs
*/
private void processPendingUploadsSynchronous(Account account,
ContentResolver resolver, String[] accountIdArgs) {
// Find the Sent folder (since that's all we're uploading for now
Cursor mailboxes = resolver.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION,
MailboxColumns.ACCOUNT_KEY + "=?"
+ " and " + MailboxColumns.TYPE + "=" + Mailbox.TYPE_SENT,
accountIdArgs, null);
long lastMessageId = -1;
try {
// Defer setting up the store until we know we need to access it
Store remoteStore = null;
while (mailboxes.moveToNext()) {
long mailboxId = mailboxes.getLong(Mailbox.ID_PROJECTION_COLUMN);
String[] mailboxKeyArgs = new String[] { Long.toString(mailboxId) };
// Demand load mailbox
Mailbox mailbox = null;
// First handle the "new" messages (serverId == null)
Cursor upsyncs1 = resolver.query(EmailContent.Message.CONTENT_URI,
EmailContent.Message.ID_PROJECTION,
EmailContent.Message.MAILBOX_KEY + "=?"
+ " and (" + EmailContent.Message.SERVER_ID + " is null"
+ " or " + EmailContent.Message.SERVER_ID + "=''" + ")",
mailboxKeyArgs,
null);
try {
while (upsyncs1.moveToNext()) {
// Load the remote store if it will be needed
if (remoteStore == null) {
remoteStore = Store.getInstance(account, mContext);
}
// Load the mailbox if it will be needed
if (mailbox == null) {
mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
if (mailbox == null) {
continue; // Mailbox removed. Move to the next message.
}
}
// upsync the message
long id = upsyncs1.getLong(EmailContent.Message.ID_PROJECTION_COLUMN);
lastMessageId = id;
processUploadMessage(resolver, remoteStore, account, mailbox, id);
}
} finally {
if (upsyncs1 != null) {
upsyncs1.close();
}
}
// Next, handle any updates (e.g. edited in place, although this shouldn't happen)
Cursor upsyncs2 = resolver.query(EmailContent.Message.UPDATED_CONTENT_URI,
EmailContent.Message.ID_PROJECTION,
EmailContent.MessageColumns.MAILBOX_KEY + "=?", mailboxKeyArgs,
null);
try {
while (upsyncs2.moveToNext()) {
// Load the remote store if it will be needed
if (remoteStore == null) {
remoteStore = Store.getInstance(account, mContext);
}
// Load the mailbox if it will be needed
if (mailbox == null) {
mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
if (mailbox == null) {
continue; // Mailbox removed. Move to the next message.
}
}
// upsync the message
long id = upsyncs2.getLong(EmailContent.Message.ID_PROJECTION_COLUMN);
lastMessageId = id;
processUploadMessage(resolver, remoteStore, account, mailbox, id);
}
} finally {
if (upsyncs2 != null) {
upsyncs2.close();
}
}
}
} catch (MessagingException me) {
// Presumably an error here is an account connection failure, so there is
// no point in continuing through the rest of the pending updates.
if (Email.DEBUG) {
Log.d(Logging.LOG_TAG, "Unable to process pending upsync for id="
+ lastMessageId + ": " + me);
}
} finally {
if (mailboxes != null) {
mailboxes.close();
}
}
}
/**
* Scan for messages that are in the Message_Updates table, look for differences that
* we can deal with, and do the work.
*
* @param account
* @param resolver
* @param accountIdArgs
*/
private void processPendingUpdatesSynchronous(Account account,
ContentResolver resolver, String[] accountIdArgs) {
Cursor updates = resolver.query(EmailContent.Message.UPDATED_CONTENT_URI,
EmailContent.Message.CONTENT_PROJECTION,
EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs,
EmailContent.MessageColumns.MAILBOX_KEY);
long lastMessageId = -1;
try {
// Defer setting up the store until we know we need to access it
Store remoteStore = null;
// Demand load mailbox (note order-by to reduce thrashing here)
Mailbox mailbox = null;
// loop through messages marked as needing updates
while (updates.moveToNext()) {
boolean changeMoveToTrash = false;
boolean changeRead = false;
boolean changeFlagged = false;
boolean changeMailbox = false;
boolean changeAnswered = false;
EmailContent.Message oldMessage =
EmailContent.getContent(updates, EmailContent.Message.class);
lastMessageId = oldMessage.mId;
EmailContent.Message newMessage =
EmailContent.Message.restoreMessageWithId(mContext, oldMessage.mId);
if (newMessage != null) {
mailbox = Mailbox.restoreMailboxWithId(mContext, newMessage.mMailboxKey);
if (mailbox == null) {
continue; // Mailbox removed. Move to the next message.
}
if (oldMessage.mMailboxKey != newMessage.mMailboxKey) {
if (mailbox.mType == Mailbox.TYPE_TRASH) {
changeMoveToTrash = true;
} else {
changeMailbox = true;
}
}
changeRead = oldMessage.mFlagRead != newMessage.mFlagRead;
changeFlagged = oldMessage.mFlagFavorite != newMessage.mFlagFavorite;
changeAnswered = (oldMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) !=
(newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO);
}
// Load the remote store if it will be needed
if (remoteStore == null &&
(changeMoveToTrash || changeRead || changeFlagged || changeMailbox ||
changeAnswered)) {
remoteStore = Store.getInstance(account, mContext);
}
// Dispatch here for specific change types
if (changeMoveToTrash) {
// Move message to trash
processPendingMoveToTrash(remoteStore, account, mailbox, oldMessage,
newMessage);
} else if (changeRead || changeFlagged || changeMailbox || changeAnswered) {
processPendingDataChange(remoteStore, mailbox, changeRead, changeFlagged,
changeMailbox, changeAnswered, oldMessage, newMessage);
}
// Finally, delete the update
Uri uri = ContentUris.withAppendedId(EmailContent.Message.UPDATED_CONTENT_URI,
oldMessage.mId);
resolver.delete(uri, null, null);
}
} catch (MessagingException me) {
// Presumably an error here is an account connection failure, so there is
// no point in continuing through the rest of the pending updates.
if (Email.DEBUG) {
Log.d(Logging.LOG_TAG, "Unable to process pending update for id="
+ lastMessageId + ": " + me);
}
} finally {
updates.close();
}
}
/**
* Upsync an entire message. This must also unwind whatever triggered it (either by
* updating the serverId, or by deleting the update record, or it's going to keep happening
* over and over again.
*
* Note: If the message is being uploaded into an unexpected mailbox, we *do not* upload.
* This is to avoid unnecessary uploads into the trash. Although the caller attempts to select
* only the Drafts and Sent folders, this can happen when the update record and the current
* record mismatch. In this case, we let the update record remain, because the filters
* in processPendingUpdatesSynchronous() will pick it up as a move and handle it (or drop it)
* appropriately.
*
* @param resolver
* @param remoteStore
* @param account
* @param mailbox the actual mailbox
* @param messageId
*/
private void processUploadMessage(ContentResolver resolver, Store remoteStore,
Account account, Mailbox mailbox, long messageId)
throws MessagingException {
EmailContent.Message newMessage =
EmailContent.Message.restoreMessageWithId(mContext, messageId);
boolean deleteUpdate = false;
if (newMessage == null) {
deleteUpdate = true;
Log.d(Logging.LOG_TAG, "Upsync failed for null message, id=" + messageId);
} else if (mailbox.mType == Mailbox.TYPE_DRAFTS) {
deleteUpdate = false;
Log.d(Logging.LOG_TAG, "Upsync skipped for mailbox=drafts, id=" + messageId);
} else if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
deleteUpdate = false;
Log.d(Logging.LOG_TAG, "Upsync skipped for mailbox=outbox, id=" + messageId);
} else if (mailbox.mType == Mailbox.TYPE_TRASH) {
deleteUpdate = false;
Log.d(Logging.LOG_TAG, "Upsync skipped for mailbox=trash, id=" + messageId);
} else if (newMessage != null && newMessage.mMailboxKey != mailbox.mId) {
deleteUpdate = false;
Log.d(Logging.LOG_TAG, "Upsync skipped; mailbox changed, id=" + messageId);
} else {
Log.d(Logging.LOG_TAG, "Upsyc triggered for message id=" + messageId);
deleteUpdate = processPendingAppend(remoteStore, account, mailbox, newMessage);
}
if (deleteUpdate) {
// Finally, delete the update (if any)
Uri uri = ContentUris.withAppendedId(
EmailContent.Message.UPDATED_CONTENT_URI, messageId);
resolver.delete(uri, null, null);
}
}
/**
* Upsync changes to read, flagged, or mailbox
*
* @param remoteStore the remote store for this mailbox
* @param mailbox the mailbox the message is stored in
* @param changeRead whether the message's read state has changed
* @param changeFlagged whether the message's flagged state has changed
* @param changeMailbox whether the message's mailbox has changed
* @param oldMessage the message in it's pre-change state
* @param newMessage the current version of the message
*/
private void processPendingDataChange(Store remoteStore, Mailbox mailbox, boolean changeRead,
boolean changeFlagged, boolean changeMailbox, boolean changeAnswered,
EmailContent.Message oldMessage, final EmailContent.Message newMessage)
throws MessagingException {
// New mailbox is the mailbox this message WILL be in (same as the one it WAS in if it isn't
// being moved
Mailbox newMailbox = mailbox;
// Mailbox is the original remote mailbox (the one we're acting on)
mailbox = getRemoteMailboxForMessage(oldMessage);
// 0. No remote update if the message is local-only
if (newMessage.mServerId == null || newMessage.mServerId.equals("")
|| newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX) || (mailbox == null)) {
return;
}
// 1. No remote update for DRAFTS or OUTBOX
if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
return;
}
// 2. Open the remote store & folder
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
if (!remoteFolder.exists()) {
return;
}
remoteFolder.open(OpenMode.READ_WRITE);
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
return;
}
// 3. Finally, apply the changes to the message
Message remoteMessage = remoteFolder.getMessage(newMessage.mServerId);
if (remoteMessage == null) {
return;
}
if (Email.DEBUG) {
Log.d(Logging.LOG_TAG,
"Update for msg id=" + newMessage.mId
+ " read=" + newMessage.mFlagRead
+ " flagged=" + newMessage.mFlagFavorite
+ " answered="
+ ((newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0)
+ " new mailbox=" + newMessage.mMailboxKey);
}
Message[] messages = new Message[] { remoteMessage };
if (changeRead) {
remoteFolder.setFlags(messages, FLAG_LIST_SEEN, newMessage.mFlagRead);
}
if (changeFlagged) {
remoteFolder.setFlags(messages, FLAG_LIST_FLAGGED, newMessage.mFlagFavorite);
}
if (changeAnswered) {
remoteFolder.setFlags(messages, FLAG_LIST_ANSWERED,
(newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0);
}
if (changeMailbox) {
Folder toFolder = remoteStore.getFolder(newMailbox.mServerId);
if (!remoteFolder.exists()) {
return;
}
// We may need the message id to search for the message in the destination folder
remoteMessage.setMessageId(newMessage.mMessageId);
// Copy the message to its new folder
remoteFolder.copyMessages(messages, toFolder, new MessageUpdateCallbacks() {
@Override
public void onMessageUidChange(Message message, String newUid) {
ContentValues cv = new ContentValues();
cv.put(EmailContent.Message.SERVER_ID, newUid);
// We only have one message, so, any updates _must_ be for it. Otherwise,
// we'd have to cycle through to find the one with the same server ID.
mContext.getContentResolver().update(ContentUris.withAppendedId(
EmailContent.Message.CONTENT_URI, newMessage.mId), cv, null, null);
}
@Override
public void onMessageNotFound(Message message) {
}
});
// Delete the message from the remote source folder
remoteMessage.setFlag(Flag.DELETED, true);
remoteFolder.expunge();
}
remoteFolder.close(false);
}
/**
* Process a pending trash message command.
*
* @param remoteStore the remote store we're working in
* @param account The account in which we are working
* @param newMailbox The local trash mailbox
* @param oldMessage The message copy that was saved in the updates shadow table
* @param newMessage The message that was moved to the mailbox
*/
private void processPendingMoveToTrash(Store remoteStore,
Account account, Mailbox newMailbox, EmailContent.Message oldMessage,
final EmailContent.Message newMessage) throws MessagingException {
// 0. No remote move if the message is local-only
if (newMessage.mServerId == null || newMessage.mServerId.equals("")
|| newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX)) {
return;
}
// 1. Escape early if we can't find the local mailbox
// TODO smaller projection here
Mailbox oldMailbox = getRemoteMailboxForMessage(oldMessage);
if (oldMailbox == null) {
// can't find old mailbox, it may have been deleted. just return.
return;
}
// 2. We don't support delete-from-trash here
if (oldMailbox.mType == Mailbox.TYPE_TRASH) {
return;
}
// 3. If DELETE_POLICY_NEVER, simply write back the deleted sentinel and return
//
// This sentinel takes the place of the server-side message, and locally "deletes" it
// by inhibiting future sync or display of the message. It will eventually go out of
// scope when it becomes old, or is deleted on the server, and the regular sync code
// will clean it up for us.
if (account.getDeletePolicy() == Account.DELETE_POLICY_NEVER) {
EmailContent.Message sentinel = new EmailContent.Message();
sentinel.mAccountKey = oldMessage.mAccountKey;
sentinel.mMailboxKey = oldMessage.mMailboxKey;
sentinel.mFlagLoaded = EmailContent.Message.FLAG_LOADED_DELETED;
sentinel.mFlagRead = true;
sentinel.mServerId = oldMessage.mServerId;
sentinel.save(mContext);
return;
}
// The rest of this method handles server-side deletion
// 4. Find the remote mailbox (that we deleted from), and open it
Folder remoteFolder = remoteStore.getFolder(oldMailbox.mServerId);
if (!remoteFolder.exists()) {
return;
}
remoteFolder.open(OpenMode.READ_WRITE);
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
remoteFolder.close(false);
return;
}
// 5. Find the remote original message
Message remoteMessage = remoteFolder.getMessage(oldMessage.mServerId);
if (remoteMessage == null) {
remoteFolder.close(false);
return;
}
// 6. Find the remote trash folder, and create it if not found
Folder remoteTrashFolder = remoteStore.getFolder(newMailbox.mServerId);
if (!remoteTrashFolder.exists()) {
/*
* If the remote trash folder doesn't exist we try to create it.
*/
remoteTrashFolder.create(FolderType.HOLDS_MESSAGES);
}
// 7. Try to copy the message into the remote trash folder
// Note, this entire section will be skipped for POP3 because there's no remote trash
if (remoteTrashFolder.exists()) {
/*
* Because remoteTrashFolder may be new, we need to explicitly open it
*/
remoteTrashFolder.open(OpenMode.READ_WRITE);
if (remoteTrashFolder.getMode() != OpenMode.READ_WRITE) {
remoteFolder.close(false);
remoteTrashFolder.close(false);
return;
}
remoteFolder.copyMessages(new Message[] { remoteMessage }, remoteTrashFolder,
new Folder.MessageUpdateCallbacks() {
@Override
public void onMessageUidChange(Message message, String newUid) {
// update the UID in the local trash folder, because some stores will
// have to change it when copying to remoteTrashFolder
ContentValues cv = new ContentValues();
cv.put(EmailContent.Message.SERVER_ID, newUid);
mContext.getContentResolver().update(newMessage.getUri(), cv, null, null);
}
/**
* This will be called if the deleted message doesn't exist and can't be
* deleted (e.g. it was already deleted from the server.) In this case,
* attempt to delete the local copy as well.
*/
@Override
public void onMessageNotFound(Message message) {
mContext.getContentResolver().delete(newMessage.getUri(), null, null);
}
});
remoteTrashFolder.close(false);
}
// 8. Delete the message from the remote source folder
remoteMessage.setFlag(Flag.DELETED, true);
remoteFolder.expunge();
remoteFolder.close(false);
}
/**
* Process a pending trash message command.
*
* @param remoteStore the remote store we're working in
* @param account The account in which we are working
* @param oldMailbox The local trash mailbox
* @param oldMessage The message that was deleted from the trash
*/
private void processPendingDeleteFromTrash(Store remoteStore,
Account account, Mailbox oldMailbox, EmailContent.Message oldMessage)
throws MessagingException {
// 1. We only support delete-from-trash here
if (oldMailbox.mType != Mailbox.TYPE_TRASH) {
return;
}
// 2. Find the remote trash folder (that we are deleting from), and open it
Folder remoteTrashFolder = remoteStore.getFolder(oldMailbox.mServerId);
if (!remoteTrashFolder.exists()) {
return;
}
remoteTrashFolder.open(OpenMode.READ_WRITE);
if (remoteTrashFolder.getMode() != OpenMode.READ_WRITE) {
remoteTrashFolder.close(false);
return;
}
// 3. Find the remote original message
Message remoteMessage = remoteTrashFolder.getMessage(oldMessage.mServerId);
if (remoteMessage == null) {
remoteTrashFolder.close(false);
return;
}
// 4. Delete the message from the remote trash folder
remoteMessage.setFlag(Flag.DELETED, true);
remoteTrashFolder.expunge();
remoteTrashFolder.close(false);
}
/** /**
* Process a pending append message command. This command uploads a local message to the * Process a pending append message command. This command uploads a local message to the
* server, first checking to be sure that the server message is not newer than * server, first checking to be sure that the server message is not newer than

View File

@ -16,15 +16,15 @@
package com.android.email; package com.android.email;
import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.utility.Utility;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.utility.Utility;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -107,11 +107,13 @@ public class RefreshManager {
private long mLastRefreshTime; private long mLastRefreshTime;
public boolean isRefreshing() { public boolean isRefreshing() {
return mIsRefreshRequested || mIsRefreshing; // NOTE: For now, we're always allowing refresh (during service refactor)
return false; //return mIsRefreshRequested || mIsRefreshing;
} }
public boolean canRefresh() { public boolean canRefresh() {
return !isRefreshing(); // NOTE: For now, we're always allowing refresh (during service refactor)
return true; //return !isRefreshing();
} }
public void onRefreshRequested() { public void onRefreshRequested() {

View File

@ -155,6 +155,7 @@ public class AttachmentDownloadService extends Service implements Runnable {
@Override @Override
public void onReceive(final Context context, Intent intent) { public void onReceive(final Context context, Intent intent) {
new Thread(new Runnable() { new Thread(new Runnable() {
@Override
public void run() { public void run() {
watchdogAlarm(); watchdogAlarm();
} }
@ -652,6 +653,7 @@ public class AttachmentDownloadService extends Service implements Runnable {
* single callback that's defined by the EmailServiceCallback interface. * single callback that's defined by the EmailServiceCallback interface.
*/ */
private class ServiceCallback extends IEmailServiceCallback.Stub { private class ServiceCallback extends IEmailServiceCallback.Stub {
@Override
public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode, public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode,
int progress) { int progress) {
// Record status and progress // Record status and progress
@ -697,6 +699,11 @@ public class AttachmentDownloadService extends Service implements Runnable {
public void syncMailboxStatus(long mailboxId, int statusCode, int progress) public void syncMailboxStatus(long mailboxId, int statusCode, int progress)
throws RemoteException { throws RemoteException {
} }
@Override
public void loadMessageStatus(long messageId, int statusCode, int progress)
throws RemoteException {
}
} }
/** /**
@ -801,6 +808,7 @@ public class AttachmentDownloadService extends Service implements Runnable {
*/ */
public static void attachmentChanged(final Context context, final long id, final int flags) { public static void attachmentChanged(final Context context, final long id, final int flags) {
Utility.runAsync(new Runnable() { Utility.runAsync(new Runnable() {
@Override
public void run() { public void run() {
Attachment attachment = Attachment.restoreAttachmentWithId(context, id); Attachment attachment = Attachment.restoreAttachmentWithId(context, id);
if (attachment != null) { if (attachment != null) {
@ -867,6 +875,7 @@ public class AttachmentDownloadService extends Service implements Runnable {
} }
} }
@Override
public void run() { public void run() {
// These fields are only used within the service thread // These fields are only used within the service thread
mContext = this; mContext = this;

View File

@ -48,7 +48,7 @@ public class EmailServiceUtils {
* @param context * @param context
* @param callback Object to get callback, or can be null * @param callback Object to get callback, or can be null
*/ */
public static IEmailService getService(Context context, String intentAction, public static EmailServiceProxy getService(Context context, String intentAction,
IEmailServiceCallback callback) { IEmailServiceCallback callback) {
return new EmailServiceProxy(context, intentAction, callback); return new EmailServiceProxy(context, intentAction, callback);
} }
@ -64,11 +64,16 @@ public class EmailServiceUtils {
startService(context, EmailServiceProxy.EXCHANGE_INTENT); startService(context, EmailServiceProxy.EXCHANGE_INTENT);
} }
public static IEmailService getExchangeService(Context context, public static EmailServiceProxy getExchangeService(Context context,
IEmailServiceCallback callback) { IEmailServiceCallback callback) {
return getService(context, EmailServiceProxy.EXCHANGE_INTENT, callback); return getService(context, EmailServiceProxy.EXCHANGE_INTENT, callback);
} }
public static EmailServiceProxy getImapService(Context context,
IEmailServiceCallback callback) {
return new EmailServiceProxy(context, ImapService.class, callback);
}
public static boolean isExchangeAvailable(Context context) { public static boolean isExchangeAvailable(Context context) {
return isServiceAvailable(context, EmailServiceProxy.EXCHANGE_INTENT); return isServiceAvailable(context, EmailServiceProxy.EXCHANGE_INTENT);
} }
@ -85,65 +90,83 @@ public class EmailServiceUtils {
public static class NullEmailService extends Service implements IEmailService { public static class NullEmailService extends Service implements IEmailService {
public static final NullEmailService INSTANCE = new NullEmailService(); public static final NullEmailService INSTANCE = new NullEmailService();
@Override
public int getApiLevel() { public int getApiLevel() {
return Api.LEVEL; return Api.LEVEL;
} }
@Override
public Bundle autoDiscover(String userName, String password) throws RemoteException { public Bundle autoDiscover(String userName, String password) throws RemoteException {
return Bundle.EMPTY; return Bundle.EMPTY;
} }
@Override
public boolean createFolder(long accountId, String name) throws RemoteException { public boolean createFolder(long accountId, String name) throws RemoteException {
return false; return false;
} }
@Override
public boolean deleteFolder(long accountId, String name) throws RemoteException { public boolean deleteFolder(long accountId, String name) throws RemoteException {
return false; return false;
} }
@Override
public void hostChanged(long accountId) throws RemoteException { public void hostChanged(long accountId) throws RemoteException {
} }
@Override
public void loadAttachment(long attachmentId, boolean background) throws RemoteException { public void loadAttachment(long attachmentId, boolean background) throws RemoteException {
} }
@Override
public void loadMore(long messageId) throws RemoteException { public void loadMore(long messageId) throws RemoteException {
} }
@Override
public boolean renameFolder(long accountId, String oldName, String newName) public boolean renameFolder(long accountId, String oldName, String newName)
throws RemoteException { throws RemoteException {
return false; return false;
} }
@Override
public void sendMeetingResponse(long messageId, int response) throws RemoteException { public void sendMeetingResponse(long messageId, int response) throws RemoteException {
} }
@Override
public void setCallback(IEmailServiceCallback cb) throws RemoteException { public void setCallback(IEmailServiceCallback cb) throws RemoteException {
} }
@Override
public void setLogging(int flags) throws RemoteException { public void setLogging(int flags) throws RemoteException {
} }
@Override
public void startSync(long mailboxId, boolean userRequest) throws RemoteException { public void startSync(long mailboxId, boolean userRequest) throws RemoteException {
} }
@Override
public void stopSync(long mailboxId) throws RemoteException { public void stopSync(long mailboxId) throws RemoteException {
} }
@Override
public void updateFolderList(long accountId) throws RemoteException { public void updateFolderList(long accountId) throws RemoteException {
} }
@Override
public Bundle validate(HostAuth hostAuth) throws RemoteException { public Bundle validate(HostAuth hostAuth) throws RemoteException {
return null; return null;
} }
@Override
public void deleteAccountPIMData(long accountId) throws RemoteException { public void deleteAccountPIMData(long accountId) throws RemoteException {
} }
@Override
public int searchMessages(long accountId, SearchParams searchParams, long destMailboxId) { public int searchMessages(long accountId, SearchParams searchParams, long destMailboxId) {
return 0; return 0;
} }
@Override
public IBinder asBinder() { public IBinder asBinder() {
return null; return null;
} }

File diff suppressed because it is too large Load Diff

View File

@ -69,8 +69,6 @@ public class MailService extends Service {
"com.android.email.intent.action.MAIL_SERVICE_CANCEL"; "com.android.email.intent.action.MAIL_SERVICE_CANCEL";
private static final String ACTION_SEND_PENDING_MAIL = private static final String ACTION_SEND_PENDING_MAIL =
"com.android.email.intent.action.MAIL_SERVICE_SEND_PENDING"; "com.android.email.intent.action.MAIL_SERVICE_SEND_PENDING";
private static final String ACTION_DELETE_EXCHANGE_ACCOUNTS =
"com.android.email.intent.action.MAIL_SERVICE_DELETE_EXCHANGE_ACCOUNTS";
private static final String EXTRA_ACCOUNT = "com.android.email.intent.extra.ACCOUNT"; 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_ACCOUNT_INFO = "com.android.email.intent.extra.ACCOUNT_INFO";
@ -114,13 +112,6 @@ public class MailService extends Service {
context.startService(i); context.startService(i);
} }
public static void actionDeleteExchangeAccounts(Context context) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_DELETE_EXCHANGE_ACCOUNTS);
context.startService(i);
}
/** /**
* Entry point for AttachmentDownloadService to ask that pending mail be sent * Entry point for AttachmentDownloadService to ask that pending mail be sent
* @param context the caller's context * @param context the caller's context
@ -157,7 +148,7 @@ public class MailService extends Service {
final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
if (ACTION_CHECK_MAIL.equals(action)) { if ((ACTION_CHECK_MAIL).equals(action)) {
// DB access required to satisfy this intent, so offload from UI thread // DB access required to satisfy this intent, so offload from UI thread
EmailAsyncTask.runAsyncParallel(new Runnable() { EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override @Override
@ -180,7 +171,10 @@ public class MailService extends Service {
synchronized(mSyncReports) { synchronized(mSyncReports) {
for (AccountSyncReport report: mSyncReports.values()) { for (AccountSyncReport report: mSyncReports.values()) {
if (report.accountId == accountId) { if (report.accountId == accountId) {
if (report.syncEnabled) { // 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, syncStarted = syncOneAccount(mController, accountId,
startId); startId);
} }
@ -211,31 +205,6 @@ public class MailService extends Service {
cancel(); cancel();
stopSelf(startId); stopSelf(startId);
} }
else if (ACTION_DELETE_EXCHANGE_ACCOUNTS.equals(action)) {
if (Email.DEBUG) {
Log.d(LOG_TAG, "action: delete exchange accounts");
}
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
Cursor c = mContentResolver.query(Account.CONTENT_URI, Account.ID_PROJECTION,
null, null, null);
try {
while (c.moveToNext()) {
long accountId = c.getLong(Account.ID_PROJECTION_COLUMN);
if ("eas".equals(Account.getProtocol(mContext, accountId))) {
// Always log this
Log.d(LOG_TAG, "Deleting EAS account: " + accountId);
mController.deleteAccountSync(accountId, mContext);
}
}
} finally {
c.close();
}
}
});
stopSelf(startId);
}
else if (ACTION_SEND_PENDING_MAIL.equals(action)) { else if (ACTION_SEND_PENDING_MAIL.equals(action)) {
if (Email.DEBUG) { if (Email.DEBUG) {
Log.d(LOG_TAG, "action: send pending mail"); Log.d(LOG_TAG, "action: send pending mail");

View File

@ -16,7 +16,6 @@
package com.android.email.service; package com.android.email.service;
import android.accounts.Account;
import android.accounts.OperationCanceledException; import android.accounts.OperationCanceledException;
import android.app.Service; import android.app.Service;
import android.content.AbstractThreadedSyncAdapter; import android.content.AbstractThreadedSyncAdapter;
@ -30,11 +29,15 @@ import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import android.util.Log;
import com.android.email.Controller; import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.EmailContent; import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns; 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.provider.Mailbox;
import java.util.ArrayList;
public class PopImapSyncAdapterService extends Service { public class PopImapSyncAdapterService extends Service {
private static final String TAG = "PopImapSyncAdapterService"; private static final String TAG = "PopImapSyncAdapterService";
private static SyncAdapterImpl sSyncAdapter = null; private static SyncAdapterImpl sSyncAdapter = null;
@ -53,7 +56,7 @@ public class PopImapSyncAdapterService extends Service {
} }
@Override @Override
public void onPerformSync(Account account, Bundle extras, public void onPerformSync(android.accounts.Account account, Bundle extras,
String authority, ContentProviderClient provider, SyncResult syncResult) { String authority, ContentProviderClient provider, SyncResult syncResult) {
try { try {
PopImapSyncAdapterService.performSync(mContext, account, extras, PopImapSyncAdapterService.performSync(mContext, account, extras,
@ -78,29 +81,96 @@ public class PopImapSyncAdapterService extends Service {
return sSyncAdapter.getSyncAdapterBinder(); return sSyncAdapter.getSyncAdapterBinder();
} }
private static void sync(Context context, long mailboxId, SyncResult syncResult) {
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
if (mailbox == null) return;
Log.d(TAG, "Mailbox: " + mailbox.mDisplayName);
Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
if (account == null) return;
try {
ImapService.synchronizeMailboxSynchronous(context, account, mailbox);
} catch (MessagingException e) {
int cause = e.getExceptionType();
switch(cause) {
case MessagingException.IOERROR:
syncResult.stats.numIoExceptions++;
break;
case MessagingException.AUTHENTICATION_FAILED:
syncResult.stats.numAuthExceptions++;
break;
}
}
}
/** /**
* Partial integration with system SyncManager; we initiate manual syncs upon request * Partial integration with system SyncManager; we initiate manual syncs upon request
*/ */
private static void performSync(Context context, Account account, Bundle extras, private static void performSync(Context context, android.accounts.Account account,
String authority, ContentProviderClient provider, SyncResult syncResult) Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)
throws OperationCanceledException { throws OperationCanceledException {
if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) { // Find an EmailProvider account with the Account's email address
String emailAddress = account.name; Cursor c = null;
// Find an EmailProvider account with the Account's email address try {
Cursor c = context.getContentResolver().query( c = provider.query(com.android.emailcommon.provider.Account.CONTENT_URI,
com.android.emailcommon.provider.Account.CONTENT_URI, Account.CONTENT_PROJECTION, AccountColumns.EMAIL_ADDRESS + "=?",
EmailContent.ID_PROJECTION, AccountColumns.EMAIL_ADDRESS + "=?", new String[] {account.name}, null);
new String[] {emailAddress}, null); if (c != null && c.moveToNext()) {
if (c.moveToNext()) { Account acct = new Account();
// If we have one, find the inbox and start it syncing acct.restore(c);
long accountId = c.getLong(EmailContent.ID_PROJECTION_COLUMN); String protocol = acct.getProtocol(context);
long mailboxId = Mailbox.findMailboxOfType(context, accountId, if (protocol.equals(HostAuth.SCHEME_IMAP)) {
Mailbox.TYPE_INBOX); if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD)) {
if (mailboxId > 0) { Log.d(TAG, "Upload sync request for " + acct.mDisplayName);
Log.d(TAG, "Starting manual sync for account " + emailAddress); // See if any boxes have mail...
Controller.getInstance(context).updateMailbox(accountId, mailboxId, false); 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);
}
} }
} }
} catch (Exception e) {
e.printStackTrace();
} finally {
if (c != null) {
c.close();
}
} }
} }
} }