PopImapSyncAdapterService.java revision 5c523858385176c33a7456bb84035de78552d22d
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.service; 18 19import android.accounts.OperationCanceledException; 20import android.app.Service; 21import android.content.AbstractThreadedSyncAdapter; 22import android.content.ContentProviderClient; 23import android.content.ContentResolver; 24import android.content.ContentUris; 25import android.content.ContentValues; 26import android.content.Context; 27import android.content.Intent; 28import android.content.SyncResult; 29import android.database.Cursor; 30import android.net.Uri; 31import android.os.Bundle; 32import android.os.IBinder; 33import android.util.Log; 34 35import com.android.email.R; 36import com.android.emailcommon.TempDirectory; 37import com.android.emailcommon.mail.MessagingException; 38import com.android.emailcommon.provider.Account; 39import com.android.emailcommon.provider.EmailContent; 40import com.android.emailcommon.provider.EmailContent.AccountColumns; 41import com.android.emailcommon.provider.EmailContent.Message; 42import com.android.emailcommon.provider.HostAuth; 43import com.android.emailcommon.provider.Mailbox; 44import com.android.emailcommon.service.EmailServiceProxy; 45 46import java.util.ArrayList; 47 48public class PopImapSyncAdapterService extends Service { 49 private static final String TAG = "PopImapSyncAdapterService"; 50 private SyncAdapterImpl mSyncAdapter = null; 51 private static final Object sSyncAdapterLock = new Object(); 52 53 public PopImapSyncAdapterService() { 54 super(); 55 } 56 57 private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter { 58 private Context mContext; 59 60 public SyncAdapterImpl(Context context) { 61 super(context, true /* autoInitialize */); 62 mContext = context; 63 } 64 65 @Override 66 public void onPerformSync(android.accounts.Account account, Bundle extras, 67 String authority, ContentProviderClient provider, SyncResult syncResult) { 68 try { 69 PopImapSyncAdapterService.performSync(mContext, account, extras, 70 authority, provider, syncResult); 71 } catch (OperationCanceledException e) { 72 } 73 } 74 } 75 76 @Override 77 public void onCreate() { 78 super.onCreate(); 79 synchronized (sSyncAdapterLock) { 80 mSyncAdapter = new SyncAdapterImpl(getApplicationContext()); 81 } 82 } 83 84 @Override 85 public IBinder onBind(Intent intent) { 86 return mSyncAdapter.getSyncAdapterBinder(); 87 } 88 89 /** 90 * @return whether or not this mailbox retrieves its data from the server (as opposed to just 91 * a local mailbox that is never synced). 92 */ 93 private static boolean loadsFromServer(Context context, Mailbox m, String protocol) { 94 String legacyImapProtocol = context.getString(R.string.protocol_legacy_imap); 95 if (legacyImapProtocol.equals(protocol)) { 96 // TODO: actually use a sync flag when creating the mailboxes. Right now we use an 97 // approximation for IMAP. 98 return m.mType != Mailbox.TYPE_DRAFTS 99 && m.mType != Mailbox.TYPE_OUTBOX 100 && m.mType != Mailbox.TYPE_SEARCH; 101 102 } else if (HostAuth.LEGACY_SCHEME_POP3.equals(protocol)) { 103 return Mailbox.TYPE_INBOX == m.mType; 104 } 105 106 return false; 107 } 108 109 private static void sync(Context context, long mailboxId, SyncResult syncResult, 110 boolean uiRefresh) { 111 TempDirectory.setTempDirectory(context); 112 Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId); 113 if (mailbox == null) return; 114 Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey); 115 if (account == null) return; 116 ContentResolver resolver = context.getContentResolver(); 117 String protocol = account.getProtocol(context); 118 if ((mailbox.mType != Mailbox.TYPE_OUTBOX) && 119 !loadsFromServer(context, mailbox, protocol)) { 120 // This is an update to a message in a non-syncing mailbox; delete this from the 121 // updates table and return 122 resolver.delete(Message.UPDATED_CONTENT_URI, Message.MAILBOX_KEY + "=?", 123 new String[] {Long.toString(mailbox.mId)}); 124 return; 125 } 126 Log.d(TAG, "Mailbox: " + mailbox.mDisplayName); 127 128 Uri mailboxUri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId); 129 ContentValues values = new ContentValues(); 130 // Set mailbox sync state 131 values.put(Mailbox.UI_SYNC_STATUS, 132 uiRefresh ? EmailContent.SYNC_STATUS_USER : EmailContent.SYNC_STATUS_BACKGROUND); 133 resolver.update(mailboxUri, values, null, null); 134 try { 135 try { 136 String legacyImapProtocol = context.getString(R.string.protocol_legacy_imap); 137 if (mailbox.mType == Mailbox.TYPE_OUTBOX) { 138 EmailServiceStub.sendMailImpl(context, account.mId); 139 } else if (protocol.equals(legacyImapProtocol)) { 140 ImapService.synchronizeMailboxSynchronous(context, account, mailbox); 141 } else { 142 Pop3Service.synchronizeMailboxSynchronous(context, account, mailbox); 143 } 144 } catch (MessagingException e) { 145 int cause = e.getExceptionType(); 146 switch(cause) { 147 case MessagingException.IOERROR: 148 syncResult.stats.numIoExceptions++; 149 break; 150 case MessagingException.AUTHENTICATION_FAILED: 151 syncResult.stats.numAuthExceptions++; 152 break; 153 } 154 } 155 } finally { 156 // Always clear our sync state 157 values.put(Mailbox.UI_SYNC_STATUS, EmailContent.SYNC_STATUS_NONE); 158 resolver.update(mailboxUri, values, null, null); 159 } 160 } 161 162 /** 163 * Partial integration with system SyncManager; we initiate manual syncs upon request 164 */ 165 private static void performSync(Context context, android.accounts.Account account, 166 Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) 167 throws OperationCanceledException { 168 // Find an EmailProvider account with the Account's email address 169 Cursor c = null; 170 try { 171 c = provider.query(com.android.emailcommon.provider.Account.CONTENT_URI, 172 Account.CONTENT_PROJECTION, AccountColumns.EMAIL_ADDRESS + "=?", 173 new String[] {account.name}, null); 174 if (c != null && c.moveToNext()) { 175 Account acct = new Account(); 176 acct.restore(c); 177 if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD)) { 178 Log.d(TAG, "Upload sync request for " + acct.mDisplayName); 179 // See if any boxes have mail... 180 ArrayList<Long> mailboxesToUpdate; 181 Cursor updatesCursor = provider.query(Message.UPDATED_CONTENT_URI, 182 new String[] {Message.MAILBOX_KEY}, 183 Message.ACCOUNT_KEY + "=?", 184 new String[] {Long.toString(acct.mId)}, 185 null); 186 try { 187 if ((updatesCursor == null) || (updatesCursor.getCount() == 0)) return; 188 mailboxesToUpdate = new ArrayList<Long>(); 189 while (updatesCursor.moveToNext()) { 190 Long mailboxId = updatesCursor.getLong(0); 191 if (!mailboxesToUpdate.contains(mailboxId)) { 192 mailboxesToUpdate.add(mailboxId); 193 } 194 } 195 } finally { 196 if (updatesCursor != null) { 197 updatesCursor.close(); 198 } 199 } 200 for (long mailboxId: mailboxesToUpdate) { 201 sync(context, mailboxId, syncResult, false); 202 } 203 } else { 204 Log.d(TAG, "Sync request for " + acct.mDisplayName); 205 Log.d(TAG, extras.toString()); 206 long mailboxId = extras.getLong(EmailServiceStub.SYNC_EXTRA_MAILBOX_ID, 207 Mailbox.NO_MAILBOX); 208 boolean isInbox = false; 209 if (mailboxId == Mailbox.NO_MAILBOX) { 210 mailboxId = Mailbox.findMailboxOfType(context, acct.mId, 211 Mailbox.TYPE_INBOX); 212 if (mailboxId == Mailbox.NO_MAILBOX) { 213 // Update folders? 214 EmailServiceProxy service = 215 EmailServiceUtils.getServiceForAccount(context, null, acct.mId); 216 service.updateFolderList(acct.mId); 217 } 218 isInbox = true; 219 } 220 if (mailboxId == Mailbox.NO_MAILBOX) return; 221 boolean uiRefresh = 222 extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); 223 sync(context, mailboxId, syncResult, uiRefresh); 224 225 // Outbox is a special case here 226 Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId); 227 if (mailbox.mType == Mailbox.TYPE_OUTBOX) { 228 return; 229 } 230 231 // Convert from minutes to seconds 232 int syncFrequency = acct.mSyncInterval * 60; 233 // Values < 0 are for "never" or "push"; 0 is undefined 234 if (syncFrequency <= 0) return; 235 Bundle ex = new Bundle(); 236 if (!isInbox) { 237 ex.putLong(EmailServiceStub.SYNC_EXTRA_MAILBOX_ID, mailboxId); 238 } 239 Log.d(TAG, "Setting periodic sync for " + acct.mDisplayName + ": " + 240 syncFrequency + " seconds"); 241 ContentResolver.addPeriodicSync(account, authority, ex, syncFrequency); 242 } 243 } 244 } catch (Exception e) { 245 e.printStackTrace(); 246 } finally { 247 if (c != null) { 248 c.close(); 249 } 250 } 251 } 252}