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.exchange.service;
18
19import android.content.AbstractThreadedSyncAdapter;
20import android.content.ContentProviderClient;
21import android.content.Context;
22import android.content.Intent;
23import android.content.SyncResult;
24import android.os.Bundle;
25import android.os.IBinder;
26import android.os.RemoteException;
27import android.text.format.DateUtils;
28import android.util.Log;
29
30import com.android.emailcommon.provider.Account;
31import com.android.emailcommon.provider.Mailbox;
32import com.android.emailcommon.service.EmailServiceStatus;
33import com.android.emailcommon.service.IEmailService;
34import com.android.exchange.Eas;
35import com.android.mail.utils.LogUtils;
36
37public class EmailSyncAdapterService extends AbstractSyncAdapterService {
38
39    private static final String TAG = Eas.LOG_TAG;
40
41    // The call to ServiceConnection.onServiceConnected is asynchronous to bindService. It's
42    // possible for that to be delayed if, in which case, a call to onPerformSync
43    // could occur before we have a connection to the service.
44    // In onPerformSync, if we don't yet have our EasService, we will wait for up to 10
45    // seconds for it to appear. If it takes longer than that, we will fail the sync.
46    private static final long MAX_WAIT_FOR_SERVICE_MS = 10 * DateUtils.SECOND_IN_MILLIS;
47
48    // TODO: Do we need to use this?
49    private static final long SYNC_ERROR_BACKOFF_MILLIS = 5 * DateUtils.MINUTE_IN_MILLIS;
50
51    /**
52     * TODO: restore this functionality.
53     * The amount of time between periodic syncs intended to ensure that push hasn't died.
54     */
55    private static final long KICK_SYNC_INTERVAL =
56            DateUtils.HOUR_IN_MILLIS / DateUtils.SECOND_IN_MILLIS;
57    /** Controls whether we do a periodic "kick" to restart the ping. */
58    private static final boolean SCHEDULE_KICK = true;
59
60    private static final Object sSyncAdapterLock = new Object();
61    private static AbstractThreadedSyncAdapter sSyncAdapter = null;
62
63    public EmailSyncAdapterService() {
64        super();
65    }
66
67    @Override
68    public void onCreate() {
69        LogUtils.v(TAG, "EmailSyncAdapterService.onCreate()");
70        super.onCreate();
71        startService(new Intent(this, EmailSyncAdapterService.class));
72    }
73
74    @Override
75    public void onDestroy() {
76        LogUtils.v(TAG, "EmailSyncAdapterService.onDestroy()");
77        super.onDestroy();
78    }
79
80    @Override
81    public IBinder onBind(Intent intent) {
82        return super.onBind(intent);
83    }
84
85    @Override
86    protected AbstractThreadedSyncAdapter getSyncAdapter() {
87        synchronized (sSyncAdapterLock) {
88            if (sSyncAdapter == null) {
89                sSyncAdapter = new SyncAdapterImpl(this);
90            }
91            return sSyncAdapter;
92        }
93    }
94
95    // TODO: Handle cancelSync() appropriately.
96    private class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
97        public SyncAdapterImpl(Context context) {
98            super(context, true /* autoInitialize */);
99        }
100
101        @Override
102        public void onPerformSync(final android.accounts.Account acct, final Bundle extras,
103                final String authority, final ContentProviderClient provider,
104                final SyncResult syncResult) {
105            if (LogUtils.isLoggable(TAG, Log.DEBUG)) {
106                LogUtils.d(TAG, "onPerformSync email: %s, %s", acct.toString(), extras.toString());
107            } else {
108                LogUtils.i(TAG, "onPerformSync email: %s", extras.toString());
109            }
110            if (!waitForService()) {
111                // The service didn't connect, nothing we can do.
112                return;
113            }
114
115            // TODO: Perform any connectivity checks, bail early if we don't have proper network
116            // for this sync operation.
117            // FLAG: Do we actually need to do this? I don't think the sync manager will invoke
118            // a sync if we don't have good network.
119
120            final Account emailAccount = Account.restoreAccountWithAddress(
121                    EmailSyncAdapterService.this, acct.name);
122            if (emailAccount == null) {
123                // There could be a timing issue with onPerformSync() being called and
124                // the account being removed from our database.
125                LogUtils.w(TAG,
126                        "onPerformSync() - Could not find an Account, skipping email sync.");
127                return;
128            }
129
130            // Push only means this sync request should only refresh the ping (either because
131            // settings changed, or we need to restart it for some reason).
132            final boolean pushOnly = Mailbox.isPushOnlyExtras(extras);
133
134            if (pushOnly) {
135                LogUtils.d(TAG, "onPerformSync: mailbox push only");
136                try {
137                    mEasService.pushModify(emailAccount.mId);
138                    return;
139                } catch (final RemoteException re) {
140                    LogUtils.e(TAG, re, "While trying to pushModify within onPerformSync");
141                    // TODO: how to handle this?
142                }
143                return;
144            } else {
145                try {
146                    final int result = mEasService.sync(emailAccount.mId, extras);
147                    writeResultToSyncResult(result, syncResult);
148                    if (syncResult.stats.numAuthExceptions > 0 &&
149                            result != EmailServiceStatus.PROVISIONING_ERROR) {
150                        showAuthNotification(emailAccount.mId, emailAccount.mEmailAddress);
151                    }
152                } catch (RemoteException e) {
153                     LogUtils.e(TAG, e, "While trying to pushModify within onPerformSync");
154                }
155            }
156
157            LogUtils.d(TAG, "onPerformSync email: finished");
158        }
159    }
160}
161