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