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