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