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 17 package com.android.email.service; 18 19 import android.app.Service; 20 import android.content.AbstractThreadedSyncAdapter; 21 import android.content.ContentProviderClient; 22 import android.content.ContentResolver; 23 import android.content.ContentUris; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.SyncResult; 28 import android.database.Cursor; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.IBinder; 32 33 import com.android.email.R; 34 import com.android.emailcommon.TempDirectory; 35 import com.android.emailcommon.mail.MessagingException; 36 import com.android.emailcommon.provider.Account; 37 import com.android.emailcommon.provider.EmailContent; 38 import com.android.emailcommon.provider.EmailContent.AccountColumns; 39 import com.android.emailcommon.provider.EmailContent.Message; 40 import com.android.emailcommon.provider.Mailbox; 41 import com.android.emailcommon.service.EmailServiceProxy; 42 import com.android.emailcommon.service.EmailServiceStatus; 43 import com.android.mail.providers.UIProvider; 44 import com.android.mail.utils.LogUtils; 45 46 import java.util.ArrayList; 47 48 public class PopImapSyncAdapterService extends Service { 49 private static final String TAG = "PopImapSyncService"; 50 private SyncAdapterImpl mSyncAdapter = null; 51 PopImapSyncAdapterService()52 public PopImapSyncAdapterService() { 53 super(); 54 } 55 56 private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter { SyncAdapterImpl(Context context)57 public SyncAdapterImpl(Context context) { 58 super(context, true /* autoInitialize */); 59 } 60 61 @Override onPerformSync(android.accounts.Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)62 public void onPerformSync(android.accounts.Account account, Bundle extras, 63 String authority, ContentProviderClient provider, SyncResult syncResult) { 64 PopImapSyncAdapterService.performSync(getContext(), account, extras, provider, 65 syncResult); 66 } 67 } 68 69 @Override onCreate()70 public void onCreate() { 71 super.onCreate(); 72 mSyncAdapter = new SyncAdapterImpl(getApplicationContext()); 73 } 74 75 @Override onBind(Intent intent)76 public IBinder onBind(Intent intent) { 77 return mSyncAdapter.getSyncAdapterBinder(); 78 } 79 80 /** 81 * @return whether or not this mailbox retrieves its data from the server (as opposed to just 82 * a local mailbox that is never synced). 83 */ loadsFromServer(Context context, Mailbox m, String protocol)84 private static boolean loadsFromServer(Context context, Mailbox m, String protocol) { 85 String legacyImapProtocol = context.getString(R.string.protocol_legacy_imap); 86 String pop3Protocol = context.getString(R.string.protocol_pop3); 87 if (legacyImapProtocol.equals(protocol)) { 88 // TODO: actually use a sync flag when creating the mailboxes. Right now we use an 89 // approximation for IMAP. 90 return m.mType != Mailbox.TYPE_DRAFTS 91 && m.mType != Mailbox.TYPE_OUTBOX 92 && m.mType != Mailbox.TYPE_SEARCH; 93 94 } else if (pop3Protocol.equals(protocol)) { 95 return Mailbox.TYPE_INBOX == m.mType; 96 } 97 98 return false; 99 } 100 sync(final Context context, final long mailboxId, final Bundle extras, final SyncResult syncResult, final boolean uiRefresh, final int deltaMessageCount)101 private static void sync(final Context context, final long mailboxId, 102 final Bundle extras, final SyncResult syncResult, final boolean uiRefresh, 103 final int deltaMessageCount) { 104 TempDirectory.setTempDirectory(context); 105 Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId); 106 if (mailbox == null) return; 107 Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey); 108 if (account == null) return; 109 ContentResolver resolver = context.getContentResolver(); 110 String protocol = account.getProtocol(context); 111 if ((mailbox.mType != Mailbox.TYPE_OUTBOX) && 112 !loadsFromServer(context, mailbox, protocol)) { 113 // This is an update to a message in a non-syncing mailbox; delete this from the 114 // updates table and return 115 resolver.delete(Message.UPDATED_CONTENT_URI, Message.MAILBOX_KEY + "=?", 116 new String[] {Long.toString(mailbox.mId)}); 117 return; 118 } 119 LogUtils.d(TAG, "About to sync mailbox: " + mailbox.mDisplayName); 120 121 Uri mailboxUri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId); 122 ContentValues values = new ContentValues(); 123 // Set mailbox sync state 124 values.put(Mailbox.UI_SYNC_STATUS, 125 uiRefresh ? EmailContent.SYNC_STATUS_USER : EmailContent.SYNC_STATUS_BACKGROUND); 126 resolver.update(mailboxUri, values, null, null); 127 try { 128 try { 129 String legacyImapProtocol = context.getString(R.string.protocol_legacy_imap); 130 if (mailbox.mType == Mailbox.TYPE_OUTBOX) { 131 EmailServiceStub.sendMailImpl(context, account.mId); 132 } else { 133 EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, 134 EmailServiceStatus.IN_PROGRESS, 0, UIProvider.LastSyncResult.SUCCESS); 135 final int status; 136 if (protocol.equals(legacyImapProtocol)) { 137 status = ImapService.synchronizeMailboxSynchronous(context, account, 138 mailbox, deltaMessageCount != 0, uiRefresh); 139 } else { 140 status = Pop3Service.synchronizeMailboxSynchronous(context, account, 141 mailbox, deltaMessageCount); 142 } 143 EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, status, 0, 144 UIProvider.LastSyncResult.SUCCESS); 145 } 146 } catch (MessagingException e) { 147 int cause = e.getExceptionType(); 148 // XXX It's no good to put the MessagingException.cause here, that's not the 149 // same set of values that we use in EmailServiceStatus. 150 switch(cause) { 151 case MessagingException.IOERROR: 152 EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0, 153 UIProvider.LastSyncResult.CONNECTION_ERROR); 154 syncResult.stats.numIoExceptions++; 155 break; 156 case MessagingException.AUTHENTICATION_FAILED: 157 EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0, 158 UIProvider.LastSyncResult.AUTH_ERROR); 159 syncResult.stats.numAuthExceptions++; 160 break; 161 162 default: 163 EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0, 164 UIProvider.LastSyncResult.INTERNAL_ERROR); 165 } 166 } 167 } finally { 168 // Always clear our sync state and update sync time. 169 values.put(Mailbox.UI_SYNC_STATUS, EmailContent.SYNC_STATUS_NONE); 170 values.put(Mailbox.SYNC_TIME, System.currentTimeMillis()); 171 resolver.update(mailboxUri, values, null, null); 172 } 173 } 174 175 /** 176 * Partial integration with system SyncManager; we initiate manual syncs upon request 177 */ performSync(Context context, android.accounts.Account account, Bundle extras, ContentProviderClient provider, SyncResult syncResult)178 private static void performSync(Context context, android.accounts.Account account, 179 Bundle extras, ContentProviderClient provider, SyncResult syncResult) { 180 // Find an EmailProvider account with the Account's email address 181 Cursor c = null; 182 try { 183 c = provider.query(com.android.emailcommon.provider.Account.CONTENT_URI, 184 Account.CONTENT_PROJECTION, AccountColumns.EMAIL_ADDRESS + "=?", 185 new String[] {account.name}, null); 186 if (c != null && c.moveToNext()) { 187 Account acct = new Account(); 188 acct.restore(c); 189 if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD)) { 190 LogUtils.d(TAG, "Upload sync request for " + acct.mDisplayName); 191 // See if any boxes have mail... 192 ArrayList<Long> mailboxesToUpdate; 193 Cursor updatesCursor = provider.query(Message.UPDATED_CONTENT_URI, 194 new String[] {Message.MAILBOX_KEY}, 195 Message.ACCOUNT_KEY + "=?", 196 new String[] {Long.toString(acct.mId)}, 197 null); 198 try { 199 if ((updatesCursor == null) || (updatesCursor.getCount() == 0)) return; 200 mailboxesToUpdate = new ArrayList<Long>(); 201 while (updatesCursor.moveToNext()) { 202 Long mailboxId = updatesCursor.getLong(0); 203 if (!mailboxesToUpdate.contains(mailboxId)) { 204 mailboxesToUpdate.add(mailboxId); 205 } 206 } 207 } finally { 208 if (updatesCursor != null) { 209 updatesCursor.close(); 210 } 211 } 212 for (long mailboxId: mailboxesToUpdate) { 213 sync(context, mailboxId, extras, syncResult, false, 0); 214 } 215 } else { 216 LogUtils.d(TAG, "Sync request for " + acct.mDisplayName); 217 LogUtils.d(TAG, extras.toString()); 218 219 // We update our folder structure on every sync. 220 final EmailServiceProxy service = 221 EmailServiceUtils.getServiceForAccount(context, acct.mId); 222 service.updateFolderList(acct.mId); 223 224 // Get the id for the mailbox we want to sync. 225 long [] mailboxIds = Mailbox.getMailboxIdsFromBundle(extras); 226 if (mailboxIds == null || mailboxIds.length == 0) { 227 // No mailbox specified, just sync the inbox. 228 // TODO: IMAP may eventually want to allow multiple auto-sync mailboxes. 229 final long inboxId = Mailbox.findMailboxOfType(context, acct.mId, 230 Mailbox.TYPE_INBOX); 231 if (inboxId != Mailbox.NO_MAILBOX) { 232 mailboxIds = new long[1]; 233 mailboxIds[0] = inboxId; 234 } 235 } 236 237 if (mailboxIds != null) { 238 boolean uiRefresh = 239 extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); 240 int deltaMessageCount = 241 extras.getInt(Mailbox.SYNC_EXTRA_DELTA_MESSAGE_COUNT, 0); 242 for (long mailboxId : mailboxIds) { 243 sync(context, mailboxId, extras, syncResult, uiRefresh, deltaMessageCount); 244 } 245 } 246 } 247 } 248 } catch (Exception e) { 249 e.printStackTrace(); 250 } finally { 251 if (c != null) { 252 c.close(); 253 } 254 } 255 } 256 } 257