AbstractSyncService.java revision 1403386ebffa1b6093a506a6a24db4523acdc315
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.email.mail.MessagingException;
21import com.android.email.provider.EmailContent.Account;
22import com.android.email.provider.EmailContent.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.util.Log;
30
31import java.util.ArrayList;
32
33/**
34 * Base class for all protocol services SyncManager (extends Service, implements
35 * Runnable) instantiates subclasses to run a sync (either timed, or push, or
36 * mail placed in outbox, etc.) EasSyncService is currently implemented; my goal
37 * would be to move IMAP to this structure when it comes time to introduce push
38 * functionality.
39 */
40public abstract class AbstractSyncService implements Runnable {
41
42    public String TAG = "AbstractSyncService";
43
44    public static final String SUMMARY_PROTOCOL = "_SUMMARY_";
45    public static final String SYNCED_PROTOCOL = "_SYNCING_";
46    public static final String MOVE_FAVORITES_PROTOCOL = "_MOVE_FAVORITES_";
47    public static final int SECONDS = 1000;
48    public static final int MINUTES = 60*SECONDS;
49    public static final int HOURS = 60*MINUTES;
50    public static final int DAYS = 24*HOURS;
51
52    public static final int CONNECT_TIMEOUT = 30*SECONDS;
53    public static final int NETWORK_WAIT = 15*SECONDS;
54
55    public static final String IMAP_PROTOCOL = "imap";
56    public static final String EAS_PROTOCOL = "eas";
57    public static final int EXIT_DONE = 0;
58    public static final int EXIT_IO_ERROR = 1;
59    public static final int EXIT_LOGIN_FAILURE = 2;
60    public static final int EXIT_EXCEPTION = 3;
61
62    public Mailbox mMailbox;
63    protected long mMailboxId;
64    protected Thread mThread;
65    protected int mExitStatus = EXIT_EXCEPTION;
66    protected String mMailboxName;
67    public Account mAccount;
68    public Context mContext;
69    public int mChangeCount = 0;
70    public int mSyncReason = 0;
71    protected volatile boolean mStop = false;
72    protected Object mSynchronizer = new Object();
73
74    protected volatile long mRequestTime = 0;
75    protected ArrayList<PartRequest> mPartRequests = new ArrayList<PartRequest>();
76    protected PartRequest mPendingPartRequest = null;
77
78    /**
79     * Sent by SyncManager to request that the service stop itself cleanly
80     */
81    public abstract void stop();
82
83    /**
84     * Sent by SyncManager to indicate a user request requiring service has been
85     * added to the service's pending request queue
86     */
87    public abstract void ping();
88
89    /**
90     * Called to validate an account; abstract to allow each protocol to do what
91     * is necessary. For consistency with the Email app's original
92     * functionality, success is indicated by a failure to throw an Exception
93     * (ugh). Parameters are self-explanatory
94     *
95     * @param host
96     * @param userName
97     * @param password
98     * @param port
99     * @param ssl
100     * @param context
101     * @throws MessagingException
102     */
103    public abstract void validateAccount(String host, String userName, String password, int port,
104            boolean ssl, Context context) throws MessagingException;
105
106    public AbstractSyncService(Context _context, Mailbox _mailbox) {
107        mContext = _context;
108        mMailbox = _mailbox;
109        mMailboxId = _mailbox.mId;
110        mMailboxName = _mailbox.mServerId;
111        mAccount = Account.restoreAccountWithId(_context, _mailbox.mAccountKey);
112    }
113
114    // Will be required when subclasses are instantiated by name
115    public AbstractSyncService(String prefix) {
116    }
117
118    /**
119     * The UI can call this static method to perform account validation.  This method wraps each
120     * protocol's validateAccount method.   Arguments are self-explanatory, except where noted.
121     *
122     * @param klass the protocol class (EasSyncService.class for example)
123     * @param host
124     * @param userName
125     * @param password
126     * @param port
127     * @param ssl
128     * @param context
129     * @throws MessagingException
130     */
131    static public void validate(Class<? extends AbstractSyncService> klass, String host,
132            String userName, String password, int port, boolean ssl, Context context)
133            throws MessagingException {
134        AbstractSyncService svc;
135        try {
136            svc = klass.newInstance();
137            svc.validateAccount(host, userName, password, port, ssl, context);
138        } catch (IllegalAccessException e) {
139            throw new MessagingException("internal error", e);
140        } catch (InstantiationException e) {
141            throw new MessagingException("internal error", e);
142        }
143    }
144
145    public static class ValidationResult {
146        static final int NO_FAILURE = 0;
147        static final int CONNECTION_FAILURE = 1;
148        static final int VALIDATION_FAILURE = 2;
149        static final int EXCEPTION = 3;
150
151        static final ValidationResult succeeded = new ValidationResult(true, NO_FAILURE, null);
152        boolean success;
153        int failure = NO_FAILURE;
154        String reason = null;
155        Exception exception = null;
156
157        ValidationResult(boolean _success, int _failure, String _reason) {
158            success = _success;
159            failure = _failure;
160            reason = _reason;
161        }
162
163        ValidationResult(boolean _success) {
164            success = _success;
165        }
166
167        ValidationResult(Exception e) {
168            success = false;
169            failure = EXCEPTION;
170            exception = e;
171        }
172
173        public boolean isSuccess() {
174            return success;
175        }
176
177        public String getReason() {
178            return reason;
179        }
180    }
181
182    public boolean isStopped() {
183        return mStop;
184    }
185
186    public Object getSynchronizer() {
187        return mSynchronizer;
188    }
189
190    /**
191     * Convenience methods to do user logging (i.e. connection activity).  Saves a bunch of
192     * repetitive code.
193     */
194    public void userLog(String string, int code, String string2) {
195        if (Eas.USER_LOG) {
196            userLog(string + code + string2);
197        }
198    }
199
200    public void userLog(String string, int code) {
201        if (Eas.USER_LOG) {
202            userLog(string + code);
203        }
204    }
205
206    public void userLog(Exception e) {
207        if (Eas.FILE_LOG) {
208            FileLogger.log(e);
209        }
210    }
211
212    /**
213     * Standard logging for EAS.
214     * If user logging is active, we concatenate any arguments and log them using Log.d
215     * We also check for file logging, and log appropriately
216     * @param strings strings to concatenate and log
217     */
218    public void userLog(String ...strings) {
219        if (Eas.USER_LOG) {
220            String logText;
221            if (strings.length == 1) {
222                logText = strings[0];
223            } else {
224                StringBuilder sb = new StringBuilder(64);
225                for (String string: strings) {
226                    sb.append(string);
227                }
228                logText = sb.toString();
229            }
230            Log.d(TAG, logText);
231            if (Eas.FILE_LOG) {
232                FileLogger.log(TAG, logText);
233            }
234        }
235    }
236
237    /**
238     * Error log is used for serious issues that should always be logged
239     * @param str the string to log
240     */
241    public void errorLog(String str) {
242        Log.e(TAG, str);
243        if (Eas.FILE_LOG) {
244            FileLogger.log(TAG, str);
245        }
246    }
247
248    /**
249     * Waits for up to 10 seconds for network connectivity; returns whether or not there is
250     * network connectivity.
251     *
252     * @return whether there is network connectivity
253     */
254    public boolean hasConnectivity() {
255        ConnectivityManager cm =
256            (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
257        int tries = 0;
258        while (tries++ < 1) {
259            NetworkInfo info = cm.getActiveNetworkInfo();
260            if (info != null && info.isConnected()) {
261                DetailedState state = info.getDetailedState();
262                if (state == DetailedState.CONNECTED) {
263                    return true;
264                }
265            }
266            try {
267                Thread.sleep(10*SECONDS);
268            } catch (InterruptedException e) {
269            }
270        }
271        return false;
272    }
273
274    /**
275     * PartRequest handling (common functionality)
276     * Can be overridden if desired, but IMAP/EAS both use the next three methods as-is
277     */
278
279    public void addPartRequest(PartRequest req) {
280        synchronized (mPartRequests) {
281            mPartRequests.add(req);
282            mRequestTime = System.currentTimeMillis();
283        }
284    }
285
286    public void removePartRequest(PartRequest req) {
287        synchronized (mPartRequests) {
288            mPartRequests.remove(req);
289        }
290    }
291
292    public PartRequest hasPartRequest(long emailId, String part) {
293        synchronized (mPartRequests) {
294            for (PartRequest pr : mPartRequests) {
295                if (pr.emailId == emailId && pr.loc.equals(part))
296                    return pr;
297            }
298        }
299        return null;
300    }
301
302    // cancelPartRequest is sent in response to user input to stop an attachment load
303    // that is in progress. This will almost certainly require code overriding the base
304    // functionality, as sockets may need to be closed, etc. and this functionality will be
305    // service dependent. This returns the canceled PartRequest or null
306    public PartRequest cancelPartRequest(long emailId, String part) {
307        synchronized (mPartRequests) {
308            PartRequest p = null;
309            for (PartRequest pr : mPartRequests) {
310                if (pr.emailId == emailId && pr.loc.equals(part)) {
311                    p = pr;
312                    break;
313                }
314            }
315            if (p != null) {
316                mPartRequests.remove(p);
317                return p;
318            }
319        }
320        return null;
321    }
322}
323