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}