1/*
2 * Copyright (C) 2008-2009 Marc Blank
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.exchange;
19
20import com.android.emailcommon.provider.Account;
21import com.android.emailcommon.provider.HostAuth;
22import com.android.emailcommon.provider.Mailbox;
23import com.android.exchange.utility.FileLogger;
24
25import android.content.Context;
26import android.net.ConnectivityManager;
27import android.net.NetworkInfo;
28import android.net.NetworkInfo.DetailedState;
29import android.os.Bundle;
30import android.util.Log;
31
32import java.util.concurrent.LinkedBlockingQueue;
33
34/**
35 * Base class for all protocol services SyncManager (extends Service, implements
36 * Runnable) instantiates subclasses to run a sync (either timed, or push, or
37 * mail placed in outbox, etc.) EasSyncService is currently implemented; my goal
38 * would be to move IMAP to this structure when it comes time to introduce push
39 * functionality.
40 */
41public abstract class AbstractSyncService implements Runnable {
42
43    public String TAG = "AbstractSyncService";
44
45    public static final int SECONDS = 1000;
46    public static final int MINUTES = 60*SECONDS;
47    public static final int HOURS = 60*MINUTES;
48    public static final int DAYS = 24*HOURS;
49
50    public static final int CONNECT_TIMEOUT = 30*SECONDS;
51    public static final int NETWORK_WAIT = 15*SECONDS;
52
53    public static final String EAS_PROTOCOL = "eas";
54    public static final int EXIT_DONE = 0;
55    public static final int EXIT_IO_ERROR = 1;
56    public static final int EXIT_LOGIN_FAILURE = 2;
57    public static final int EXIT_EXCEPTION = 3;
58    public static final int EXIT_SECURITY_FAILURE = 4;
59    public static final int EXIT_ACCESS_DENIED = 5;
60
61    public Mailbox mMailbox;
62    protected long mMailboxId;
63    protected int mExitStatus = EXIT_EXCEPTION;
64    protected String mMailboxName;
65    public Account mAccount;
66    public Context mContext;
67    public int mChangeCount = 0;
68    public volatile int mSyncReason = 0;
69    protected volatile boolean mStop = false;
70    protected volatile Thread mThread;
71    protected final Object mSynchronizer = new Object();
72
73    protected volatile long mRequestTime = 0;
74    protected LinkedBlockingQueue<Request> mRequestQueue = new LinkedBlockingQueue<Request>();
75
76    /**
77     * Sent by SyncManager to request that the service stop itself cleanly
78     */
79    public abstract void stop();
80
81    /**
82     * Sent by SyncManager to indicate that an alarm has fired for this service, and that its
83     * pending (network) operation has timed out. The service is NOT automatically stopped,
84     * although the behavior is service dependent.
85     *
86     * @return true if the operation was stopped normally; false if the thread needed to be
87     * interrupted.
88     */
89    public abstract boolean alarm();
90
91    /**
92     * Sent by SyncManager to request that the service reset itself cleanly; the meaning of this
93     * operation is service dependent.
94     */
95    public abstract void reset();
96
97    /**
98     * Called to validate an account; abstract to allow each protocol to do what
99     * is necessary. For consistency with the Email app's original
100     * functionality, success is indicated by a failure to throw an Exception
101     * (ugh). Parameters are self-explanatory
102     *
103     * @param hostAuth
104     * @return a Bundle containing a result code and, depending on the result, a PolicySet or an
105     * error message
106     */
107    public abstract Bundle validateAccount(HostAuth hostAuth, Context context);
108
109    public AbstractSyncService(Context _context, Mailbox _mailbox) {
110        mContext = _context;
111        mMailbox = _mailbox;
112        mMailboxId = _mailbox.mId;
113        mMailboxName = _mailbox.mServerId;
114        mAccount = Account.restoreAccountWithId(_context, _mailbox.mAccountKey);
115    }
116
117    // Will be required when subclasses are instantiated by name
118    public AbstractSyncService(String prefix) {
119    }
120
121    /**
122     * The UI can call this static method to perform account validation.  This method wraps each
123     * protocol's validateAccount method.   Arguments are self-explanatory, except where noted.
124     *
125     * @param klass the protocol class (EasSyncService.class for example)
126     * @param hostAuth
127     * @param context
128     * @return a Bundle containing a result code and, depending on the result, a PolicySet or an
129     * error message
130     */
131    public static Bundle validate(Class<? extends AbstractSyncService> klass,
132            HostAuth hostAuth, Context context) {
133        AbstractSyncService svc;
134        try {
135            svc = klass.newInstance();
136            return svc.validateAccount(hostAuth, context);
137        } catch (IllegalAccessException e) {
138        } catch (InstantiationException e) {
139        }
140        return null;
141    }
142
143    public static class ValidationResult {
144        static final int NO_FAILURE = 0;
145        static final int CONNECTION_FAILURE = 1;
146        static final int VALIDATION_FAILURE = 2;
147        static final int EXCEPTION = 3;
148
149        static final ValidationResult succeeded = new ValidationResult(true, NO_FAILURE, null);
150        boolean success;
151        int failure = NO_FAILURE;
152        String reason = null;
153        Exception exception = null;
154
155        ValidationResult(boolean _success, int _failure, String _reason) {
156            success = _success;
157            failure = _failure;
158            reason = _reason;
159        }
160
161        ValidationResult(boolean _success) {
162            success = _success;
163        }
164
165        ValidationResult(Exception e) {
166            success = false;
167            failure = EXCEPTION;
168            exception = e;
169        }
170
171        public boolean isSuccess() {
172            return success;
173        }
174
175        public String getReason() {
176            return reason;
177        }
178    }
179
180    public boolean isStopped() {
181        return mStop;
182    }
183
184    public Object getSynchronizer() {
185        return mSynchronizer;
186    }
187
188    /**
189     * Convenience methods to do user logging (i.e. connection activity).  Saves a bunch of
190     * repetitive code.
191     */
192    public void userLog(String string, int code, String string2) {
193        if (Eas.USER_LOG) {
194            userLog(string + code + string2);
195        }
196    }
197
198    public void userLog(String string, int code) {
199        if (Eas.USER_LOG) {
200            userLog(string + code);
201        }
202    }
203
204    public void userLog(String str, Exception e) {
205        if (Eas.USER_LOG) {
206            Log.e(TAG, str, e);
207        } else {
208            Log.e(TAG, str + e);
209        }
210        if (Eas.FILE_LOG) {
211            FileLogger.log(e);
212        }
213    }
214
215    /**
216     * Standard logging for EAS.
217     * If user logging is active, we concatenate any arguments and log them using Log.d
218     * We also check for file logging, and log appropriately
219     * @param strings strings to concatenate and log
220     */
221    public void userLog(String ...strings) {
222        if (Eas.USER_LOG) {
223            String logText;
224            if (strings.length == 1) {
225                logText = strings[0];
226            } else {
227                StringBuilder sb = new StringBuilder(64);
228                for (String string: strings) {
229                    sb.append(string);
230                }
231                logText = sb.toString();
232            }
233            Log.d(TAG, logText);
234            if (Eas.FILE_LOG) {
235                FileLogger.log(TAG, logText);
236            }
237        }
238    }
239
240    /**
241     * Error log is used for serious issues that should always be logged
242     * @param str the string to log
243     */
244    public void errorLog(String str) {
245        Log.e(TAG, str);
246        if (Eas.FILE_LOG) {
247            FileLogger.log(TAG, str);
248        }
249    }
250
251    /**
252     * Waits for up to 10 seconds for network connectivity; returns whether or not there is
253     * network connectivity.
254     *
255     * @return whether there is network connectivity
256     */
257    public boolean hasConnectivity() {
258        ConnectivityManager cm =
259                (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
260        int tries = 0;
261        while (tries++ < 1) {
262            // Use the same test as in ExchangeService#waitForConnectivity
263            // TODO: Create common code for this test in emailcommon
264            NetworkInfo info = cm.getActiveNetworkInfo();
265            if (info != null) {
266                return true;
267            }
268            try {
269                Thread.sleep(10*SECONDS);
270            } catch (InterruptedException e) {
271            }
272        }
273        return false;
274    }
275
276    /**
277     * Request handling (common functionality)
278     * Can be overridden if desired
279     */
280
281    public void addRequest(Request req) {
282        mRequestQueue.offer(req);
283    }
284
285    public void removeRequest(Request req) {
286        mRequestQueue.remove(req);
287    }
288
289    public boolean hasPendingRequests() {
290        return !mRequestQueue.isEmpty();
291    }
292
293    public void clearRequests() {
294        mRequestQueue.clear();
295    }
296}
297