1147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank/*
2147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank * Copyright (C) 2009 The Android Open Source Project
3147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank *
4147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank * Licensed under the Apache License, Version 2.0 (the "License");
5147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank * you may not use this file except in compliance with the License.
6147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank * You may obtain a copy of the License at
7147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank *
8147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank *      http://www.apache.org/licenses/LICENSE-2.0
9147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank *
10147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank * Unless required by applicable law or agreed to in writing, software
11147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank * distributed under the License is distributed on an "AS IS" BASIS,
12147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank * See the License for the specific language governing permissions and
14147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank * limitations under the License.
15147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank */
16147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
17bbafcf7dbcb92063aa207113451c1d29235bc5ddMarc Blankpackage com.android.exchange.service;
18147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
19147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blankimport android.content.AbstractThreadedSyncAdapter;
20147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blankimport android.content.ContentProviderClient;
21147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blankimport android.content.ContentResolver;
224fbb88b5f46f4890106fa6302f87511a72042129Marc Blankimport android.content.Context;
23147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blankimport android.content.SyncResult;
24147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blankimport android.database.Cursor;
255d5c395c2d6ff0574a73802e3adab70e907e1b53Marc Blankimport android.net.Uri;
26147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blankimport android.os.Bundle;
27f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdonimport android.os.RemoteException;
28c6f2846fe5e708abd194924df294d223b3d6a723Marc Blankimport android.provider.ContactsContract.Groups;
295d5c395c2d6ff0574a73802e3adab70e907e1b53Marc Blankimport android.provider.ContactsContract.RawContacts;
303eef378426c7c88608f53f5a268baed40259ccf6Alon Albertimport android.util.Log;
31147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
32f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdonimport com.android.emailcommon.provider.Account;
33b31070f7484eeeb15c4bce89dbc61388d05d0bfcYu Ping Huimport com.android.emailcommon.provider.EmailContent;
34b31070f7484eeeb15c4bce89dbc61388d05d0bfcYu Ping Huimport com.android.emailcommon.provider.EmailContent.MailboxColumns;
35b31070f7484eeeb15c4bce89dbc61388d05d0bfcYu Ping Huimport com.android.emailcommon.provider.Mailbox;
36f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdonimport com.android.emailcommon.service.EmailServiceStatus;
37b31070f7484eeeb15c4bce89dbc61388d05d0bfcYu Ping Huimport com.android.exchange.Eas;
38b31070f7484eeeb15c4bce89dbc61388d05d0bfcYu Ping Huimport com.android.mail.utils.LogUtils;
39b31070f7484eeeb15c4bce89dbc61388d05d0bfcYu Ping Hu
40bbafcf7dbcb92063aa207113451c1d29235bc5ddMarc Blankpublic class ContactsSyncAdapterService extends AbstractSyncAdapterService {
41110837ebff288a75f9bda067c38e2c46797d99b5Alon Albert    private static final String TAG = Eas.LOG_TAG;
42147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank    private static final String ACCOUNT_AND_TYPE_CONTACTS =
43147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.TYPE + '=' + Mailbox.TYPE_CONTACTS;
44147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
4524e1187f8511d301fa586759cd1b3bd5ad2ccf41Yu Ping Hu    private static final Object sSyncAdapterLock = new Object();
4624e1187f8511d301fa586759cd1b3bd5ad2ccf41Yu Ping Hu    private static AbstractThreadedSyncAdapter sSyncAdapter = null;
4724e1187f8511d301fa586759cd1b3bd5ad2ccf41Yu Ping Hu
48147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank    public ContactsSyncAdapterService() {
49147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        super();
50147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank    }
51147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
529696d7f701599644d1c108863d6195af88da2c30Yu Ping Hu    @Override
5324e1187f8511d301fa586759cd1b3bd5ad2ccf41Yu Ping Hu    protected AbstractThreadedSyncAdapter getSyncAdapter() {
5424e1187f8511d301fa586759cd1b3bd5ad2ccf41Yu Ping Hu        synchronized (sSyncAdapterLock) {
5524e1187f8511d301fa586759cd1b3bd5ad2ccf41Yu Ping Hu            if (sSyncAdapter == null) {
5624e1187f8511d301fa586759cd1b3bd5ad2ccf41Yu Ping Hu                sSyncAdapter = new SyncAdapterImpl(this);
5724e1187f8511d301fa586759cd1b3bd5ad2ccf41Yu Ping Hu            }
5824e1187f8511d301fa586759cd1b3bd5ad2ccf41Yu Ping Hu            return sSyncAdapter;
5924e1187f8511d301fa586759cd1b3bd5ad2ccf41Yu Ping Hu        }
609696d7f701599644d1c108863d6195af88da2c30Yu Ping Hu    }
619696d7f701599644d1c108863d6195af88da2c30Yu Ping Hu
62f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon    private class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
63fba54ab0b9b4cd3d24624c2c988e2ee422f8a59eFred Quintana        public SyncAdapterImpl(Context context) {
64fba54ab0b9b4cd3d24624c2c988e2ee422f8a59eFred Quintana            super(context, true /* autoInitialize */);
65147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        }
66147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
67147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        @Override
68f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon        public void onPerformSync(android.accounts.Account acct, Bundle extras,
69147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank                String authority, ContentProviderClient provider, SyncResult syncResult) {
703eef378426c7c88608f53f5a268baed40259ccf6Alon Albert            if (LogUtils.isLoggable(TAG, Log.DEBUG)) {
71f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                LogUtils.d(TAG, "onPerformSync contacts starting %s, %s", acct.toString(),
723eef378426c7c88608f53f5a268baed40259ccf6Alon Albert                        extras.toString());
733eef378426c7c88608f53f5a268baed40259ccf6Alon Albert            } else {
74f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                LogUtils.i(TAG, "onPerformSync contacts starting %s", extras.toString());
753eef378426c7c88608f53f5a268baed40259ccf6Alon Albert            }
76f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            if (!waitForService()) {
77f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                // The service didn't connect, nothing we can do.
78f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                return;
79f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            }
80f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon
81f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            final Account emailAccount = Account.restoreAccountWithAddress(
82f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    ContactsSyncAdapterService.this, acct.name);
83db1cd8456a0cf273890cd3da10d0d5f00aadbb45Anthony Lee            if (emailAccount == null) {
84db1cd8456a0cf273890cd3da10d0d5f00aadbb45Anthony Lee                // There could be a timing issue with onPerformSync() being called and
85db1cd8456a0cf273890cd3da10d0d5f00aadbb45Anthony Lee                // the account being removed from our database.
86db1cd8456a0cf273890cd3da10d0d5f00aadbb45Anthony Lee                LogUtils.w(TAG,
87db1cd8456a0cf273890cd3da10d0d5f00aadbb45Anthony Lee                        "onPerformSync() - Could not find an Account, skipping contacts sync.");
88db1cd8456a0cf273890cd3da10d0d5f00aadbb45Anthony Lee                return;
89db1cd8456a0cf273890cd3da10d0d5f00aadbb45Anthony Lee            }
90f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon
91f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            // TODO: is this still needed?
92f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            // If we've been asked to do an upload, make sure we've got work to do
93f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD)) {
94f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                Uri uri = RawContacts.CONTENT_URI.buildUpon()
95f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                        .appendQueryParameter(RawContacts.ACCOUNT_NAME, acct.name)
96f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                        .appendQueryParameter(RawContacts.ACCOUNT_TYPE,
97f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                                Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE)
98f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                        .build();
99f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                // See if we've got dirty contacts or dirty groups containing our contacts
100f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                boolean changed = hasDirtyRows(getContentResolver(), uri, RawContacts.DIRTY);
101f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                if (!changed) {
102f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    uri = Groups.CONTENT_URI.buildUpon()
103f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                            .appendQueryParameter(RawContacts.ACCOUNT_NAME, acct.name)
104f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                            .appendQueryParameter(RawContacts.ACCOUNT_TYPE,
105f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                                    Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE)
106f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                            .build();
107f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    changed = hasDirtyRows(getContentResolver(), uri, Groups.DIRTY);
108f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                }
109f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                if (!changed) {
110f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    LogUtils.d(TAG, "Upload sync; no changes");
111f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    return;
112f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                }
113f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            }
114f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon
115f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            // TODO: move this to some common place.
116f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            // Push only means this sync request should only refresh the ping (either because
117f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            // settings changed, or we need to restart it for some reason).
118f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            final boolean pushOnly = Mailbox.isPushOnlyExtras(extras);
119f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon
120f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            if (pushOnly) {
121f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                LogUtils.d(TAG, "onPerformSync email: mailbox push only");
122f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                if (mEasService != null) {
123f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    try {
124f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                        mEasService.pushModify(emailAccount.mId);
125f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                        return;
126f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    } catch (final RemoteException re) {
127f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                        LogUtils.e(TAG, re, "While trying to pushModify within onPerformSync");
128f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                        // TODO: how to handle this?
129f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    }
130f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                }
131f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                return;
132f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            } else {
133f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                try {
134f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    final int result = mEasService.sync(emailAccount.mId, extras);
135f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    writeResultToSyncResult(result, syncResult);
136f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    if (syncResult.stats.numAuthExceptions > 0 &&
137f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                            result != EmailServiceStatus.PROVISIONING_ERROR) {
138f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                        showAuthNotification(emailAccount.mId, emailAccount.mEmailAddress);
139f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    }
140f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                } catch (RemoteException e) {
141f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                    LogUtils.e(TAG, e, "While trying to pushModify within onPerformSync");
142f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon                }
143f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            }
144f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon
145f8cccaecc8148d12d58ffcba5ce7366191316ac0Martin Hibdon            LogUtils.d(TAG, "onPerformSync contacts: finished");
146147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        }
147147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank    }
148147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
149c6f2846fe5e708abd194924df294d223b3d6a723Marc Blank    private static boolean hasDirtyRows(ContentResolver resolver, Uri uri, String dirtyColumn) {
1506e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu        Cursor c = resolver.query(uri, EmailContent.ID_PROJECTION, dirtyColumn + "=1", null, null);
151680fcf0006c3894e21952902c568d13398a6540eJay Shrauner        if (c == null) {
152680fcf0006c3894e21952902c568d13398a6540eJay Shrauner            return false;
153680fcf0006c3894e21952902c568d13398a6540eJay Shrauner        }
154c6f2846fe5e708abd194924df294d223b3d6a723Marc Blank        try {
155c6f2846fe5e708abd194924df294d223b3d6a723Marc Blank            return c.getCount() > 0;
156c6f2846fe5e708abd194924df294d223b3d6a723Marc Blank        } finally {
157c6f2846fe5e708abd194924df294d223b3d6a723Marc Blank            c.close();
158c6f2846fe5e708abd194924df294d223b3d6a723Marc Blank        }
159c6f2846fe5e708abd194924df294d223b3d6a723Marc Blank    }
1606e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu}
161