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:
parent
80535ef38c
commit
c84467afe1
|
@ -395,6 +395,17 @@
|
|||
</intent-filter>
|
||||
</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 -->
|
||||
<service
|
||||
android:name=".service.EasAuthenticatorService"
|
||||
|
|
|
@ -16,12 +16,6 @@
|
|||
|
||||
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.Intent;
|
||||
import android.os.Bundle;
|
||||
|
@ -29,6 +23,12 @@ import android.os.IBinder;
|
|||
import android.os.RemoteException;
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -51,6 +51,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
|
||||
// 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 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_HOST_AUTH = "autodiscover_host_auth";
|
||||
|
@ -64,6 +65,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
private final IEmailServiceCallback mCallback;
|
||||
private Object mReturn = null;
|
||||
private IEmailService mService;
|
||||
private final boolean isRemote;
|
||||
|
||||
// Standard debugging
|
||||
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) {
|
||||
super(_context, new Intent(_context, _class));
|
||||
mCallback = _callback;
|
||||
isRemote = false;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
}
|
||||
mCallback = _callback;
|
||||
isRemote = true;
|
||||
}
|
||||
|
||||
public EmailServiceProxy(Context _context, String _action, IEmailServiceCallback _callback) {
|
||||
|
@ -102,6 +106,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
} catch (IOException e) {
|
||||
}
|
||||
mCallback = _callback;
|
||||
isRemote = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -109,6 +114,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
mService = IEmailService.Stub.asInterface(binder);
|
||||
}
|
||||
|
||||
public boolean isRemote() {
|
||||
return isRemote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getApiLevel() {
|
||||
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.
|
||||
* prefetch) vs a foreground action (user request)
|
||||
*/
|
||||
@Override
|
||||
public void loadAttachment(final long attachmentId, final boolean background)
|
||||
throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
try {
|
||||
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 userRequest whether or not the user specifically asked for the sync
|
||||
*/
|
||||
@Override
|
||||
public void startSync(final long mailboxId, final boolean userRequest) throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
if (mCallback != null) mService.setCallback(mCallback);
|
||||
mService.startSync(mailboxId, userRequest);
|
||||
|
@ -170,8 +183,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
* @param mailboxId the id of the mailbox record
|
||||
* @param userRequest whether or not the user specifically asked for the sync
|
||||
*/
|
||||
@Override
|
||||
public void stopSync(final long mailboxId) throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
if (mCallback != null) mService.setCallback(mCallback);
|
||||
mService.stopSync(mailboxId);
|
||||
|
@ -189,8 +204,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
* @param hostAuth the hostauth object to validate
|
||||
* @return a Bundle as described above
|
||||
*/
|
||||
@Override
|
||||
public Bundle validate(final HostAuth hostAuth) throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException{
|
||||
if (mCallback != null) mService.setCallback(mCallback);
|
||||
mReturn = mService.validate(hostAuth);
|
||||
|
@ -219,9 +236,11 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
* @param password the user's password
|
||||
* @return a Bundle as described above
|
||||
*/
|
||||
@Override
|
||||
public Bundle autoDiscover(final String userName, final String password)
|
||||
throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException{
|
||||
if (mCallback != null) mService.setCallback(mCallback);
|
||||
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
|
||||
*/
|
||||
@Override
|
||||
public void updateFolderList(final long accountId) throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
if (mCallback != null) mService.setCallback(mCallback);
|
||||
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
|
||||
*/
|
||||
@Override
|
||||
public void setLogging(final int flags) throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
if (mCallback != null) mService.setCallback(mCallback);
|
||||
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
|
||||
*/
|
||||
@Override
|
||||
public void setCallback(final IEmailServiceCallback cb) throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
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
|
||||
*/
|
||||
@Override
|
||||
public void hostChanged(final long accountId) throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
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 response the response code, as defined in EmailServiceConstants
|
||||
*/
|
||||
@Override
|
||||
public void sendMeetingResponse(final long messageId, final int response)
|
||||
throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
if (mCallback != null) mService.setCallback(mCallback);
|
||||
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
|
||||
*/
|
||||
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 name the name of the folder to be created
|
||||
*/
|
||||
@Override
|
||||
public boolean createFolder(long accountId, String name) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
@ -337,6 +375,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
* @param accountId the account in which the folder resides
|
||||
* @param name the name of the folder to be deleted
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteFolder(long accountId, String name) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
@ -348,6 +387,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
* @param oldName the name of the existing folder
|
||||
* @param newName the new name for the folder
|
||||
*/
|
||||
@Override
|
||||
public boolean renameFolder(long accountId, String oldName, String newName)
|
||||
throws RemoteException {
|
||||
return false;
|
||||
|
@ -361,8 +401,10 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
*
|
||||
* @param accountId the account whose data is to be deleted
|
||||
*/
|
||||
@Override
|
||||
public void deleteAccountPIMData(final long accountId) throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
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
|
||||
* @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,
|
||||
final long destMailboxId) throws RemoteException {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException{
|
||||
if (mCallback != null) mService.setCallback(mCallback);
|
||||
mReturn = mService.searchMessages(accountId, searchParams, destMailboxId);
|
||||
|
@ -400,6 +444,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
|||
return (Integer)mReturn;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -65,4 +65,12 @@ oneway interface IEmailServiceCallback {
|
|||
* progress = 0 for "start", 1..100 for optional progress reports
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -23,5 +23,6 @@
|
|||
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:contentAuthority="com.android.email.provider"
|
||||
android:accountType="com.android.email"
|
||||
android:supportsUploading="false"
|
||||
android:supportsUploading="true"
|
||||
android:allowParallelSyncs="true"
|
||||
/>
|
||||
|
|
|
@ -48,6 +48,7 @@ import com.android.emailcommon.provider.EmailContent.Message;
|
|||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.service.EmailServiceProxy;
|
||||
import com.android.emailcommon.service.EmailServiceStatus;
|
||||
import com.android.emailcommon.service.IEmailService;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||
|
@ -339,6 +340,7 @@ public class Controller {
|
|||
/**
|
||||
* Request a remote update of mailboxes for an account.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void updateMailboxList(final long accountId) {
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
|
@ -367,23 +369,15 @@ public class Controller {
|
|||
* 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) {
|
||||
// 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);
|
||||
// } 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
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
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
|
||||
* 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);
|
||||
|
@ -412,6 +407,7 @@ public class Controller {
|
|||
} else {
|
||||
// MessagingController implementation
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO shouldn't be passing fully-build accounts & mailboxes into APIs
|
||||
Account account =
|
||||
|
@ -438,11 +434,13 @@ 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)
|
||||
IEmailService service = getServiceForMessage(messageId);
|
||||
if (service != null) {
|
||||
EmailServiceProxy service = getServiceForMessage(messageId);
|
||||
if (service != null && service.isRemote()) {
|
||||
// Get rid of this!!
|
||||
// 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);
|
||||
|
@ -456,9 +454,16 @@ public class Controller {
|
|||
listener.loadMessageForViewCallback(null, accountId, messageId, 100);
|
||||
}
|
||||
}
|
||||
} else if (service != null) {
|
||||
// IMAP here for now
|
||||
try {
|
||||
service.loadMore(messageId);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
} else {
|
||||
// MessagingController implementation
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mLegacyController.loadMessageForView(messageId, mLegacyListener);
|
||||
}
|
||||
|
@ -593,6 +598,7 @@ 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 =
|
||||
|
@ -602,6 +608,7 @@ public class Controller {
|
|||
}
|
||||
final long sentboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_SENT);
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mLegacyController.sendPendingMessages(account, sentboxId, mLegacyListener);
|
||||
}
|
||||
|
@ -644,8 +651,10 @@ public class Controller {
|
|||
* look up limit
|
||||
* write limit into all mailboxes for that account
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void resetVisibleLimits() {
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ContentResolver resolver = mProviderContext.getContentResolver();
|
||||
Cursor c = null;
|
||||
|
@ -682,6 +691,7 @@ public class Controller {
|
|||
*/
|
||||
public void loadMoreMessages(final long mailboxId) {
|
||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(mProviderContext, mailboxId);
|
||||
if (mailbox == null) {
|
||||
|
@ -746,6 +756,7 @@ public class Controller {
|
|||
*/
|
||||
public void deleteMessage(final long messageId) {
|
||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
deleteMessageSync(messageId);
|
||||
}
|
||||
|
@ -760,6 +771,7 @@ public class Controller {
|
|||
throw new IllegalArgumentException();
|
||||
}
|
||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (long messageId: messageIds) {
|
||||
deleteMessageSync(messageId);
|
||||
|
@ -809,10 +821,6 @@ public class Controller {
|
|||
cv.put(EmailContent.MessageColumns.MAILBOX_KEY, trashMailboxId);
|
||||
resolver.update(uri, cv, null, null);
|
||||
}
|
||||
|
||||
if (isMessagingController(account)) {
|
||||
mLegacyController.processPendingActions(account.mId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -834,6 +842,7 @@ public class Controller {
|
|||
throw new IllegalArgumentException();
|
||||
}
|
||||
return EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Account account = Account.getAccountForMessageId(mProviderContext, messageIds[0]);
|
||||
if (account != null) {
|
||||
|
@ -845,9 +854,6 @@ public class Controller {
|
|||
EmailContent.Message.SYNCED_CONTENT_URI, messageId);
|
||||
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) {
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId);
|
||||
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,
|
||||
final int flag) {
|
||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Message msg = Message.restoreMessageWithId(mProviderContext, messageId);
|
||||
if (msg == null) {
|
||||
|
@ -1019,7 +1019,9 @@ public class Controller {
|
|||
if (Email.DEBUG) {
|
||||
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
|
||||
* @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.
|
||||
Message message = Message.restoreMessageWithId(mProviderContext, messageId);
|
||||
if (message == null) {
|
||||
|
@ -1100,15 +1102,22 @@ public class Controller {
|
|||
* @param accountId the message of interest
|
||||
* @result service proxy, or null if n/a
|
||||
*/
|
||||
private IEmailService getServiceForAccount(long accountId) {
|
||||
private EmailServiceProxy getServiceForAccount(long accountId) {
|
||||
if (isMessagingController(accountId)) return null;
|
||||
if (Account.getProtocol(mContext, accountId).equals(HostAuth.SCHEME_IMAP)) {
|
||||
return getImapEmailService();
|
||||
}
|
||||
return getExchangeEmailService();
|
||||
}
|
||||
|
||||
private IEmailService 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
|
||||
*/
|
||||
|
@ -1121,7 +1130,7 @@ public class Controller {
|
|||
Boolean isLegacyController = mLegacyControllerMap.get(accountId);
|
||||
if (isLegacyController == null) {
|
||||
String protocol = Account.getProtocol(mProviderContext, accountId);
|
||||
isLegacyController = ("pop3".equals(protocol) || "imap".equals(protocol));
|
||||
isLegacyController = (HostAuth.SCHEME_POP3.equals(protocol));
|
||||
mLegacyControllerMap.put(accountId, isLegacyController);
|
||||
}
|
||||
return isLegacyController;
|
||||
|
@ -1581,8 +1590,7 @@ public class Controller {
|
|||
*/
|
||||
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,
|
||||
int progress) {
|
||||
MessagingException result = mapStatusToException(statusCode);
|
||||
|
@ -1591,10 +1599,6 @@ public class Controller {
|
|||
progress = 100;
|
||||
break;
|
||||
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
|
||||
if (progress < 0 || progress >= 100) {
|
||||
return;
|
||||
|
@ -1616,6 +1620,7 @@ public class Controller {
|
|||
* However, this is sufficient for basic "progress=100" notification that message send
|
||||
* has just completed.
|
||||
*/
|
||||
@Override
|
||||
public void sendMessageStatus(long messageId, String subject, int statusCode,
|
||||
int progress) {
|
||||
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) {
|
||||
MessagingException result = mapStatusToException(statusCode);
|
||||
switch (statusCode) {
|
||||
|
@ -1658,6 +1692,7 @@ public class Controller {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxStatus(long mailboxId, int statusCode, int progress) {
|
||||
MessagingException result = mapStatusToException(statusCode);
|
||||
switch (statusCode) {
|
||||
|
@ -1752,6 +1787,7 @@ public class Controller {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentStatus(final long messageId, final long attachmentId,
|
||||
final int status, final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
|
@ -1766,6 +1802,10 @@ public class Controller {
|
|||
public void sendMessageStatus(long messageId, String subject, int statusCode, int progress){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessageStatus(long messageId, int statusCode, int progress){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxListStatus(long accountId, int statusCode, int progress) {
|
||||
}
|
||||
|
@ -1782,20 +1822,25 @@ public class Controller {
|
|||
*/
|
||||
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,
|
||||
|
@ -1835,41 +1880,52 @@ public class Controller {
|
|||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
|
|
|
@ -41,7 +41,6 @@ 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.MessageUpdateCallbacks;
|
||||
import com.android.emailcommon.mail.Folder.OpenMode;
|
||||
import com.android.emailcommon.mail.Message;
|
||||
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.SyncColumns;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.service.SearchParams;
|
||||
import com.android.emailcommon.utility.AttachmentUtilities;
|
||||
import com.android.emailcommon.utility.ConversionUtilities;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
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 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.
|
||||
*/
|
||||
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();
|
||||
static {
|
||||
PRUNE_ATTACHMENT_CV.putNull(AttachmentColumns.CONTENT_URI);
|
||||
|
@ -363,7 +347,6 @@ public class MessagingController implements Runnable {
|
|||
}
|
||||
NotificationController nc = NotificationController.getInstance(mContext);
|
||||
try {
|
||||
processPendingActionsSynchronous(account);
|
||||
|
||||
// Select generic sync or store-specific sync
|
||||
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.
|
||||
*
|
||||
|
@ -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
|
||||
* server, first checking to be sure that the server message is not newer than
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
|
||||
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.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
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.Collection;
|
||||
import java.util.HashMap;
|
||||
|
@ -107,11 +107,13 @@ public class RefreshManager {
|
|||
private long mLastRefreshTime;
|
||||
|
||||
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() {
|
||||
return !isRefreshing();
|
||||
// NOTE: For now, we're always allowing refresh (during service refactor)
|
||||
return true; //return !isRefreshing();
|
||||
}
|
||||
|
||||
public void onRefreshRequested() {
|
||||
|
|
|
@ -155,6 +155,7 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
|||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
watchdogAlarm();
|
||||
}
|
||||
|
@ -652,6 +653,7 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
|||
* single callback that's defined by the EmailServiceCallback interface.
|
||||
*/
|
||||
private class ServiceCallback extends IEmailServiceCallback.Stub {
|
||||
@Override
|
||||
public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode,
|
||||
int 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)
|
||||
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) {
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Attachment attachment = Attachment.restoreAttachmentWithId(context, id);
|
||||
if (attachment != null) {
|
||||
|
@ -867,6 +875,7 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// These fields are only used within the service thread
|
||||
mContext = this;
|
||||
|
|
|
@ -48,7 +48,7 @@ public class EmailServiceUtils {
|
|||
* @param context
|
||||
* @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) {
|
||||
return new EmailServiceProxy(context, intentAction, callback);
|
||||
}
|
||||
|
@ -64,11 +64,16 @@ public class EmailServiceUtils {
|
|||
startService(context, EmailServiceProxy.EXCHANGE_INTENT);
|
||||
}
|
||||
|
||||
public static IEmailService getExchangeService(Context context,
|
||||
public static EmailServiceProxy getExchangeService(Context context,
|
||||
IEmailServiceCallback 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) {
|
||||
return isServiceAvailable(context, EmailServiceProxy.EXCHANGE_INTENT);
|
||||
}
|
||||
|
@ -85,65 +90,83 @@ public class EmailServiceUtils {
|
|||
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;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -69,8 +69,6 @@ public class MailService extends Service {
|
|||
"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 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_INFO = "com.android.email.intent.extra.ACCOUNT_INFO";
|
||||
|
@ -114,13 +112,6 @@ public class MailService extends Service {
|
|||
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
|
||||
* @param context the caller's context
|
||||
|
@ -157,7 +148,7 @@ public class MailService extends 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
|
||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
|
@ -180,7 +171,10 @@ public class MailService extends Service {
|
|||
synchronized(mSyncReports) {
|
||||
for (AccountSyncReport report: mSyncReports.values()) {
|
||||
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,
|
||||
startId);
|
||||
}
|
||||
|
@ -211,31 +205,6 @@ public class MailService extends Service {
|
|||
cancel();
|
||||
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)) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(LOG_TAG, "action: send pending mail");
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package com.android.email.service;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.app.Service;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
|
@ -30,11 +29,15 @@ import android.os.Bundle;
|
|||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.Controller;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
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 java.util.ArrayList;
|
||||
|
||||
public class PopImapSyncAdapterService extends Service {
|
||||
private static final String TAG = "PopImapSyncAdapterService";
|
||||
private static SyncAdapterImpl sSyncAdapter = null;
|
||||
|
@ -53,7 +56,7 @@ public class PopImapSyncAdapterService extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras,
|
||||
public void onPerformSync(android.accounts.Account account, Bundle extras,
|
||||
String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
try {
|
||||
PopImapSyncAdapterService.performSync(mContext, account, extras,
|
||||
|
@ -78,29 +81,96 @@ public class PopImapSyncAdapterService extends Service {
|
|||
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
|
||||
*/
|
||||
private static void performSync(Context context, Account account, Bundle extras,
|
||||
String authority, ContentProviderClient provider, SyncResult syncResult)
|
||||
throws OperationCanceledException {
|
||||
if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
|
||||
String emailAddress = account.name;
|
||||
// Find an EmailProvider account with the Account's email address
|
||||
Cursor c = context.getContentResolver().query(
|
||||
com.android.emailcommon.provider.Account.CONTENT_URI,
|
||||
EmailContent.ID_PROJECTION, AccountColumns.EMAIL_ADDRESS + "=?",
|
||||
new String[] {emailAddress}, null);
|
||||
if (c.moveToNext()) {
|
||||
// If we have one, find the inbox and start it syncing
|
||||
long accountId = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
|
||||
long mailboxId = Mailbox.findMailboxOfType(context, accountId,
|
||||
Mailbox.TYPE_INBOX);
|
||||
if (mailboxId > 0) {
|
||||
Log.d(TAG, "Starting manual sync for account " + emailAddress);
|
||||
Controller.getInstance(context).updateMailbox(accountId, mailboxId, false);
|
||||
private static void performSync(Context context, android.accounts.Account account,
|
||||
Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)
|
||||
throws OperationCanceledException {
|
||||
// Find an EmailProvider account with the Account's email address
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = provider.query(com.android.emailcommon.provider.Account.CONTENT_URI,
|
||||
Account.CONTENT_PROJECTION, AccountColumns.EMAIL_ADDRESS + "=?",
|
||||
new String[] {account.name}, null);
|
||||
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);
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue