PopImapSyncAdapterService.java revision cc0185f07c9198008d8dc685ae9979f3e35e8539
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.emailcommon.TempDirectory; 36import com.android.emailcommon.mail.MessagingException; 37import com.android.emailcommon.provider.Account; 38import com.android.emailcommon.provider.EmailContent; 39import com.android.emailcommon.provider.EmailContent.AccountColumns; 40import com.android.emailcommon.provider.EmailContent.Message; 41import com.android.emailcommon.provider.HostAuth; 42import com.android.emailcommon.provider.Mailbox; 43import com.android.emailcommon.service.EmailServiceProxy; 44 45import java.util.ArrayList; 46 47public class PopImapSyncAdapterService extends Service { 48 private static final String TAG = "PopImapSyncAdapterService"; 49 private static SyncAdapterImpl sSyncAdapter = null; 50 private static final Object sSyncAdapterLock = new Object(); 51 52 public PopImapSyncAdapterService() { 53 super(); 54 } 55 56 private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter { 57 private Context mContext; 58 59 public SyncAdapterImpl(Context context) { 60 super(context, true /* autoInitialize */); 61 mContext = context; 62 } 63 64 @Override 65 public void onPerformSync(android.accounts.Account account, Bundle extras, 66 String authority, ContentProviderClient provider, SyncResult syncResult) { 67 try { 68 PopImapSyncAdapterService.performSync(mContext, account, extras, 69 authority, provider, syncResult); 70 } catch (OperationCanceledException e) { 71 } 72 } 73 } 74 75 @Override 76 public void onCreate() { 77 super.onCreate(); 78 synchronized (sSyncAdapterLock) { 79 if (sSyncAdapter == null) { 80 sSyncAdapter = new SyncAdapterImpl(getApplicationContext()); 81 } 82 } 83 } 84 85 @Override 86 public IBinder onBind(Intent intent) { 87 return sSyncAdapter.getSyncAdapterBinder(); 88 } 89 90 /** 91 * @return whether or not this mailbox retrieves its data from the server (as opposed to just 92 * a local mailbox that is never synced). 93 */ 94 public static boolean loadsFromServer(Mailbox m, String protocol) { 95 if (HostAuth.LEGACY_SCHEME_IMAP.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) && !loadsFromServer(mailbox, protocol)) { 119 // This is an update to a message in a non-syncing mailbox; delete this from the 120 // updates table and return 121 resolver.delete(Message.UPDATED_CONTENT_URI, Message.MAILBOX_KEY + "=?", 122 new String[] {Long.toString(mailbox.mId)}); 123 return; 124 } 125 Log.d(TAG, "Mailbox: " + mailbox.mDisplayName); 126 127 Uri mailboxUri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId); 128 ContentValues values = new ContentValues(); 129 // Set mailbox sync state 130 values.put(Mailbox.UI_SYNC_STATUS, 131 uiRefresh ? EmailContent.SYNC_STATUS_USER : EmailContent.SYNC_STATUS_BACKGROUND); 132 resolver.update(mailboxUri, values, null, null); 133 try { 134 try { 135 if (mailbox.mType == Mailbox.TYPE_OUTBOX) { 136 EmailServiceStub.sendMailImpl(context, account.mId); 137 } else if (protocol.equals(HostAuth.LEGACY_SCHEME_IMAP)) { 138 ImapService.synchronizeMailboxSynchronous(context, account, mailbox); 139 } else { 140 Pop3Service.synchronizeMailboxSynchronous(context, account, mailbox); 141 } 142 } catch (MessagingException e) { 143 int cause = e.getExceptionType(); 144 switch(cause) { 145 case MessagingException.IOERROR: 146 syncResult.stats.numIoExceptions++; 147 break; 148 case MessagingException.AUTHENTICATION_FAILED: 149 syncResult.stats.numAuthExceptions++; 150 break; 151 } 152 } 153 } finally { 154 // Always clear our sync state 155 values.put(Mailbox.UI_SYNC_STATUS, EmailContent.SYNC_STATUS_NONE); 156 resolver.update(mailboxUri, values, null, null); 157 } 158 } 159 160 /** 161 * Partial integration with system SyncManager; we initiate manual syncs upon request 162 */ 163 private static void performSync(Context context, android.accounts.Account account, 164 Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) 165 throws OperationCanceledException { 166 // Find an EmailProvider account with the Account's email address 167 Cursor c = null; 168 try { 169 c = provider.query(com.android.emailcommon.provider.Account.CONTENT_URI, 170 Account.CONTENT_PROJECTION, AccountColumns.EMAIL_ADDRESS + "=?", 171 new String[] {account.name}, null); 172 if (c != null && c.moveToNext()) { 173 Account acct = new Account(); 174 acct.restore(c); 175 if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD)) { 176 Log.d(TAG, "Upload sync request for " + acct.mDisplayName); 177 // See if any boxes have mail... 178 ArrayList<Long> mailboxesToUpdate; 179 Cursor updatesCursor = provider.query(Message.UPDATED_CONTENT_URI, 180 new String[] {Message.MAILBOX_KEY}, 181 Message.ACCOUNT_KEY + "=?", 182 new String[] {Long.toString(acct.mId)}, 183 null); 184 try { 185 if ((updatesCursor == null) || (updatesCursor.getCount() == 0)) return; 186 mailboxesToUpdate = new ArrayList<Long>(); 187 while (updatesCursor.moveToNext()) { 188 Long mailboxId = updatesCursor.getLong(0); 189 if (!mailboxesToUpdate.contains(mailboxId)) { 190 mailboxesToUpdate.add(mailboxId); 191 } 192 } 193 } finally { 194 if (updatesCursor != null) { 195 updatesCursor.close(); 196 } 197 } 198 for (long mailboxId: mailboxesToUpdate) { 199 sync(context, mailboxId, syncResult, false); 200 } 201 } else { 202 Log.d(TAG, "Sync request for " + acct.mDisplayName); 203 Log.d(TAG, extras.toString()); 204 long mailboxId = extras.getLong(EmailServiceStub.SYNC_EXTRA_MAILBOX_ID, 205 Mailbox.NO_MAILBOX); 206 boolean isInbox = false; 207 if (mailboxId == Mailbox.NO_MAILBOX) { 208 mailboxId = Mailbox.findMailboxOfType(context, acct.mId, 209 Mailbox.TYPE_INBOX); 210 if (mailboxId == Mailbox.NO_MAILBOX) { 211 // Update folders? 212 EmailServiceProxy service = 213 EmailServiceUtils.getServiceForAccount(context, null, acct.mId); 214 service.updateFolderList(acct.mId); 215 } 216 isInbox = true; 217 } 218 if (mailboxId == Mailbox.NO_MAILBOX) return; 219 boolean uiRefresh = 220 extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false); 221 sync(context, mailboxId, syncResult, uiRefresh); 222 223 // Outbox is a special case here 224 Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId); 225 if (mailbox.mType == Mailbox.TYPE_OUTBOX) { 226 return; 227 } 228 229 // Convert from minutes to seconds 230 int syncFrequency = acct.mSyncInterval * 60; 231 // Values < 0 are for "never" or "push"; 0 is undefined 232 if (syncFrequency <= 0) return; 233 Bundle ex = new Bundle(); 234 if (!isInbox) { 235 ex.putLong(EmailServiceStub.SYNC_EXTRA_MAILBOX_ID, mailboxId); 236 } 237 Log.d(TAG, "Setting periodic sync for " + acct.mDisplayName + ": " + 238 syncFrequency + " seconds"); 239 ContentResolver.addPeriodicSync(account, authority, ex, syncFrequency); 240 } 241 } 242 } catch (Exception e) { 243 e.printStackTrace(); 244 } finally { 245 if (c != null) { 246 c.close(); 247 } 248 } 249 } 250}