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