1/*
2 * Copyright (C) 2009 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.exchange.service;
18
19import android.content.AbstractThreadedSyncAdapter;
20import android.content.ContentProviderClient;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.SyncResult;
24import android.database.Cursor;
25import android.net.Uri;
26import android.os.Bundle;
27import android.os.RemoteException;
28import android.provider.ContactsContract.Groups;
29import android.provider.ContactsContract.RawContacts;
30import android.util.Log;
31
32import com.android.emailcommon.provider.Account;
33import com.android.emailcommon.provider.EmailContent;
34import com.android.emailcommon.provider.EmailContent.MailboxColumns;
35import com.android.emailcommon.provider.Mailbox;
36import com.android.emailcommon.service.EmailServiceStatus;
37import com.android.exchange.Eas;
38import com.android.mail.utils.LogUtils;
39
40public class ContactsSyncAdapterService extends AbstractSyncAdapterService {
41    private static final String TAG = Eas.LOG_TAG;
42    private static final String ACCOUNT_AND_TYPE_CONTACTS =
43        MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.TYPE + '=' + Mailbox.TYPE_CONTACTS;
44
45    private static final Object sSyncAdapterLock = new Object();
46    private static AbstractThreadedSyncAdapter sSyncAdapter = null;
47
48    public ContactsSyncAdapterService() {
49        super();
50    }
51
52    @Override
53    protected AbstractThreadedSyncAdapter getSyncAdapter() {
54        synchronized (sSyncAdapterLock) {
55            if (sSyncAdapter == null) {
56                sSyncAdapter = new SyncAdapterImpl(this);
57            }
58            return sSyncAdapter;
59        }
60    }
61
62    private class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
63        public SyncAdapterImpl(Context context) {
64            super(context, true /* autoInitialize */);
65        }
66
67        @Override
68        public void onPerformSync(android.accounts.Account acct, Bundle extras,
69                String authority, ContentProviderClient provider, SyncResult syncResult) {
70            if (LogUtils.isLoggable(TAG, Log.DEBUG)) {
71                LogUtils.d(TAG, "onPerformSync contacts starting %s, %s", acct.toString(),
72                        extras.toString());
73            } else {
74                LogUtils.i(TAG, "onPerformSync contacts starting %s", extras.toString());
75            }
76            if (!waitForService()) {
77                // The service didn't connect, nothing we can do.
78                return;
79            }
80
81            final Account emailAccount = Account.restoreAccountWithAddress(
82                    ContactsSyncAdapterService.this, acct.name);
83            if (emailAccount == null) {
84                // There could be a timing issue with onPerformSync() being called and
85                // the account being removed from our database.
86                LogUtils.w(TAG,
87                        "onPerformSync() - Could not find an Account, skipping contacts sync.");
88                return;
89            }
90
91            // TODO: is this still needed?
92            // If we've been asked to do an upload, make sure we've got work to do
93            if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD)) {
94                Uri uri = RawContacts.CONTENT_URI.buildUpon()
95                        .appendQueryParameter(RawContacts.ACCOUNT_NAME, acct.name)
96                        .appendQueryParameter(RawContacts.ACCOUNT_TYPE,
97                                Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE)
98                        .build();
99                // See if we've got dirty contacts or dirty groups containing our contacts
100                boolean changed = hasDirtyRows(getContentResolver(), uri, RawContacts.DIRTY);
101                if (!changed) {
102                    uri = Groups.CONTENT_URI.buildUpon()
103                            .appendQueryParameter(RawContacts.ACCOUNT_NAME, acct.name)
104                            .appendQueryParameter(RawContacts.ACCOUNT_TYPE,
105                                    Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE)
106                            .build();
107                    changed = hasDirtyRows(getContentResolver(), uri, Groups.DIRTY);
108                }
109                if (!changed) {
110                    LogUtils.d(TAG, "Upload sync; no changes");
111                    return;
112                }
113            }
114
115            // TODO: move this to some common place.
116            // Push only means this sync request should only refresh the ping (either because
117            // settings changed, or we need to restart it for some reason).
118            final boolean pushOnly = Mailbox.isPushOnlyExtras(extras);
119
120            if (pushOnly) {
121                LogUtils.d(TAG, "onPerformSync email: mailbox push only");
122                if (mEasService != null) {
123                    try {
124                        mEasService.pushModify(emailAccount.mId);
125                        return;
126                    } catch (final RemoteException re) {
127                        LogUtils.e(TAG, re, "While trying to pushModify within onPerformSync");
128                        // TODO: how to handle this?
129                    }
130                }
131                return;
132            } else {
133                try {
134                    final int result = mEasService.sync(emailAccount.mId, extras);
135                    writeResultToSyncResult(result, syncResult);
136                    if (syncResult.stats.numAuthExceptions > 0 &&
137                            result != EmailServiceStatus.PROVISIONING_ERROR) {
138                        showAuthNotification(emailAccount.mId, emailAccount.mEmailAddress);
139                    }
140                } catch (RemoteException e) {
141                    LogUtils.e(TAG, e, "While trying to pushModify within onPerformSync");
142                }
143            }
144
145            LogUtils.d(TAG, "onPerformSync contacts: finished");
146        }
147    }
148
149    private static boolean hasDirtyRows(ContentResolver resolver, Uri uri, String dirtyColumn) {
150        Cursor c = resolver.query(uri, EmailContent.ID_PROJECTION, dirtyColumn + "=1", null, null);
151        if (c == null) {
152            return false;
153        }
154        try {
155            return c.getCount() > 0;
156        } finally {
157            c.close();
158        }
159    }
160}
161