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