Home | History | Annotate | Download | only in exchange
      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 
     18 package com.android.exchange;
     19 
     20 import com.android.email.mail.MessagingException;
     21 import com.android.email.provider.EmailContent.Account;
     22 import com.android.email.provider.EmailContent.Mailbox;
     23 import com.android.exchange.utility.FileLogger;
     24 
     25 import android.content.ContentResolver;
     26 import android.content.ContentUris;
     27 import android.content.Context;
     28 import android.database.Cursor;
     29 import android.net.ConnectivityManager;
     30 import android.net.NetworkInfo;
     31 import android.net.Uri;
     32 import android.net.NetworkInfo.DetailedState;
     33 import android.util.Log;
     34 
     35 import java.util.ArrayList;
     36 
     37 /**
     38  * Base class for all protocol services SyncManager (extends Service, implements
     39  * Runnable) instantiates subclasses to run a sync (either timed, or push, or
     40  * mail placed in outbox, etc.) EasSyncService is currently implemented; my goal
     41  * would be to move IMAP to this structure when it comes time to introduce push
     42  * functionality.
     43  */
     44 public abstract class AbstractSyncService implements Runnable {
     45 
     46     public String TAG = "AbstractSyncService";
     47 
     48     public static final int SECONDS = 1000;
     49     public static final int MINUTES = 60*SECONDS;
     50     public static final int HOURS = 60*MINUTES;
     51     public static final int DAYS = 24*HOURS;
     52 
     53     public static final int CONNECT_TIMEOUT = 30*SECONDS;
     54     public static final int NETWORK_WAIT = 15*SECONDS;
     55 
     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     public static final int EXIT_SECURITY_FAILURE = 4;
     62 
     63     public Mailbox mMailbox;
     64     protected long mMailboxId;
     65     protected Thread mThread;
     66     protected int mExitStatus = EXIT_EXCEPTION;
     67     protected String mMailboxName;
     68     public Account mAccount;
     69     public Context mContext;
     70     public int mChangeCount = 0;
     71     public int mSyncReason = 0;
     72     protected volatile boolean mStop = false;
     73     protected Object mSynchronizer = new Object();
     74 
     75     protected volatile long mRequestTime = 0;
     76     protected ArrayList<Request> mRequests = new ArrayList<Request>();
     77     protected PartRequest mPendingRequest = null;
     78 
     79     /**
     80      * Sent by SyncManager to request that the service stop itself cleanly
     81      */
     82     public abstract void stop();
     83 
     84     /**
     85      * Sent by SyncManager to indicate that an alarm has fired for this service, and that its
     86      * pending (network) operation has timed out. The service is NOT automatically stopped,
     87      * although the behavior is service dependent.
     88      *
     89      * @return true if the operation was stopped normally; false if the thread needed to be
     90      * interrupted.
     91      */
     92     public abstract boolean alarm();
     93 
     94     /**
     95      * Sent by SyncManager to request that the service reset itself cleanly; the meaning of this
     96      * operation is service dependent.
     97      */
     98     public abstract void reset();
     99 
    100     /**
    101      * Called to validate an account; abstract to allow each protocol to do what
    102      * is necessary. For consistency with the Email app's original
    103      * functionality, success is indicated by a failure to throw an Exception
    104      * (ugh). Parameters are self-explanatory
    105      *
    106      * @param host
    107      * @param userName
    108      * @param password
    109      * @param port
    110      * @param ssl
    111      * @param context
    112      * @throws MessagingException
    113      */
    114     public abstract void validateAccount(String host, String userName, String password, int port,
    115             boolean ssl, boolean trustCertificates, Context context) throws MessagingException;
    116 
    117     public AbstractSyncService(Context _context, Mailbox _mailbox) {
    118         mContext = _context;
    119         mMailbox = _mailbox;
    120         mMailboxId = _mailbox.mId;
    121         mMailboxName = _mailbox.mServerId;
    122         mAccount = Account.restoreAccountWithId(_context, _mailbox.mAccountKey);
    123     }
    124 
    125     // Will be required when subclasses are instantiated by name
    126     public AbstractSyncService(String prefix) {
    127     }
    128 
    129     /**
    130      * The UI can call this static method to perform account validation.  This method wraps each
    131      * protocol's validateAccount method.   Arguments are self-explanatory, except where noted.
    132      *
    133      * @param klass the protocol class (EasSyncService.class for example)
    134      * @param host
    135      * @param userName
    136      * @param password
    137      * @param port
    138      * @param ssl
    139      * @param context
    140      * @throws MessagingException
    141      */
    142     static public void validate(Class<? extends AbstractSyncService> klass, String host,
    143             String userName, String password, int port, boolean ssl, boolean trustCertificates,
    144             Context context)
    145             throws MessagingException {
    146         AbstractSyncService svc;
    147         try {
    148             svc = klass.newInstance();
    149             svc.validateAccount(host, userName, password, port, ssl, trustCertificates, context);
    150         } catch (IllegalAccessException e) {
    151             throw new MessagingException("internal error", e);
    152         } catch (InstantiationException e) {
    153             throw new MessagingException("internal error", e);
    154         }
    155     }
    156 
    157     public static class ValidationResult {
    158         static final int NO_FAILURE = 0;
    159         static final int CONNECTION_FAILURE = 1;
    160         static final int VALIDATION_FAILURE = 2;
    161         static final int EXCEPTION = 3;
    162 
    163         static final ValidationResult succeeded = new ValidationResult(true, NO_FAILURE, null);
    164         boolean success;
    165         int failure = NO_FAILURE;
    166         String reason = null;
    167         Exception exception = null;
    168 
    169         ValidationResult(boolean _success, int _failure, String _reason) {
    170             success = _success;
    171             failure = _failure;
    172             reason = _reason;
    173         }
    174 
    175         ValidationResult(boolean _success) {
    176             success = _success;
    177         }
    178 
    179         ValidationResult(Exception e) {
    180             success = false;
    181             failure = EXCEPTION;
    182             exception = e;
    183         }
    184 
    185         public boolean isSuccess() {
    186             return success;
    187         }
    188 
    189         public String getReason() {
    190             return reason;
    191         }
    192     }
    193 
    194     public boolean isStopped() {
    195         return mStop;
    196     }
    197 
    198     public Object getSynchronizer() {
    199         return mSynchronizer;
    200     }
    201 
    202     /**
    203      * Convenience methods to do user logging (i.e. connection activity).  Saves a bunch of
    204      * repetitive code.
    205      */
    206     public void userLog(String string, int code, String string2) {
    207         if (Eas.USER_LOG) {
    208             userLog(string + code + string2);
    209         }
    210     }
    211 
    212     public void userLog(String string, int code) {
    213         if (Eas.USER_LOG) {
    214             userLog(string + code);
    215         }
    216     }
    217 
    218     public void userLog(String str, Exception e) {
    219         if (Eas.USER_LOG) {
    220             Log.e(TAG, str, e);
    221         } else {
    222             Log.e(TAG, str + e);
    223         }
    224         if (Eas.FILE_LOG) {
    225             FileLogger.log(e);
    226         }
    227     }
    228 
    229     /**
    230      * Standard logging for EAS.
    231      * If user logging is active, we concatenate any arguments and log them using Log.d
    232      * We also check for file logging, and log appropriately
    233      * @param strings strings to concatenate and log
    234      */
    235     public void userLog(String ...strings) {
    236         if (Eas.USER_LOG) {
    237             String logText;
    238             if (strings.length == 1) {
    239                 logText = strings[0];
    240             } else {
    241                 StringBuilder sb = new StringBuilder(64);
    242                 for (String string: strings) {
    243                     sb.append(string);
    244                 }
    245                 logText = sb.toString();
    246             }
    247             Log.d(TAG, logText);
    248             if (Eas.FILE_LOG) {
    249                 FileLogger.log(TAG, logText);
    250             }
    251         }
    252     }
    253 
    254     /**
    255      * Error log is used for serious issues that should always be logged
    256      * @param str the string to log
    257      */
    258     public void errorLog(String str) {
    259         Log.e(TAG, str);
    260         if (Eas.FILE_LOG) {
    261             FileLogger.log(TAG, str);
    262         }
    263     }
    264 
    265     /**
    266      * Waits for up to 10 seconds for network connectivity; returns whether or not there is
    267      * network connectivity.
    268      *
    269      * @return whether there is network connectivity
    270      */
    271     public boolean hasConnectivity() {
    272         ConnectivityManager cm =
    273             (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    274         int tries = 0;
    275         while (tries++ < 1) {
    276             NetworkInfo info = cm.getActiveNetworkInfo();
    277             if (info != null && info.isConnected()) {
    278                 DetailedState state = info.getDetailedState();
    279                 if (state == DetailedState.CONNECTED) {
    280                     return true;
    281                 }
    282             }
    283             try {
    284                 Thread.sleep(10*SECONDS);
    285             } catch (InterruptedException e) {
    286             }
    287         }
    288         return false;
    289     }
    290 
    291     /**
    292      * Request handling (common functionality)
    293      * Can be overridden if desired
    294      */
    295 
    296     public void addRequest(Request req) {
    297         synchronized (mRequests) {
    298             mRequests.add(req);
    299             mRequestTime = System.currentTimeMillis();
    300         }
    301     }
    302 
    303     public void removeRequest(Request req) {
    304         synchronized (mRequests) {
    305             mRequests.remove(req);
    306         }
    307     }
    308 
    309     /**
    310      * Convenience method wrapping calls to retrieve columns from a single row, via EmailProvider.
    311      * The arguments are exactly the same as to contentResolver.query().  Results are returned in
    312      * an array of Strings corresponding to the columns in the projection.
    313      */
    314     protected String[] getRowColumns(Uri contentUri, String[] projection, String selection,
    315             String[] selectionArgs) {
    316         String[] values = new String[projection.length];
    317         ContentResolver cr = mContext.getContentResolver();
    318         Cursor c = cr.query(contentUri, projection, selection, selectionArgs, null);
    319         try {
    320             if (c.moveToFirst()) {
    321                 for (int i = 0; i < projection.length; i++) {
    322                     values[i] = c.getString(i);
    323                 }
    324             } else {
    325                 return null;
    326             }
    327         } finally {
    328             c.close();
    329         }
    330         return values;
    331     }
    332 
    333     /**
    334      * Convenience method for retrieving columns from a particular row in EmailProvider.
    335      * Passed in here are a base uri (e.g. Message.CONTENT_URI), the unique id of a row, and
    336      * a projection.  This method calls the previous one with the appropriate URI.
    337      */
    338     protected String[] getRowColumns(Uri baseUri, long id, String ... projection) {
    339         return getRowColumns(ContentUris.withAppendedId(baseUri, id), projection, null, null);
    340     }
    341 }
    342