EasSyncService.java revision 8480f5bc2eb612920f7e17312a693b4d8c26f14b
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 java.io.BufferedReader; 21import java.io.BufferedWriter; 22import java.io.ByteArrayInputStream; 23import java.io.File; 24import java.io.FileNotFoundException; 25import java.io.FileOutputStream; 26import java.io.FileReader; 27import java.io.FileWriter; 28import java.io.IOException; 29import java.io.InputStream; 30import java.io.OutputStreamWriter; 31import java.net.HttpURLConnection; 32import java.net.MalformedURLException; 33import java.net.ProtocolException; 34import java.net.URI; 35import java.net.URL; 36import java.net.URLEncoder; 37 38import java.util.ArrayList; 39 40import javax.net.ssl.HttpsURLConnection; 41 42import org.apache.http.HttpEntity; 43import org.apache.http.HttpResponse; 44import org.apache.http.client.methods.HttpPost; 45import org.apache.http.conn.ssl.AllowAllHostnameVerifier; 46import org.apache.http.impl.client.DefaultHttpClient; 47 48import com.android.email.mail.AuthenticationFailedException; 49import com.android.email.mail.MessagingException; 50import com.android.exchange.EmailContent.Account; 51import com.android.exchange.EmailContent.Attachment; 52import com.android.exchange.EmailContent.AttachmentColumns; 53import com.android.exchange.EmailContent.HostAuth; 54import com.android.exchange.EmailContent.Mailbox; 55import com.android.exchange.EmailContent.MailboxColumns; 56import com.android.exchange.adapter.EasContactsSyncAdapter; 57import com.android.exchange.adapter.EasEmailSyncAdapter; 58import com.android.exchange.adapter.EasFolderSyncParser; 59import com.android.exchange.adapter.EasPingParser; 60import com.android.exchange.adapter.EasSerializer; 61import com.android.exchange.adapter.EasSyncAdapter; 62import com.android.exchange.adapter.EasParser.EasParserException; 63import com.android.exchange.utility.Base64; 64 65import android.content.ContentResolver; 66import android.content.ContentValues; 67import android.content.Context; 68import android.database.Cursor; 69import android.util.Log; 70 71public class EasSyncService extends InteractiveSyncService { 72 73 private static final String WINDOW_SIZE = "10"; 74 private static final String WHERE_ACCOUNT_KEY_AND_SERVER_ID = 75 MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SERVER_ID + "=?"; 76 private static final String WHERE_SYNC_FREQUENCY_PING = 77 Mailbox.SYNC_FREQUENCY + '=' + Account.CHECK_INTERVAL_PING; 78 private static final String SYNC_FREQUENCY_PING = 79 MailboxColumns.SYNC_FREQUENCY + '=' + Account.CHECK_INTERVAL_PING; 80 81 // Reasonable default 82 String mProtocolVersion = "2.5"; 83 static String mDeviceId = null; 84 static String mDeviceType = "Android"; 85 EasSyncAdapter mTarget; 86 String mAuthString = null; 87 String mCmdString = null; 88 String mVersions; 89 public String mHostAddress; 90 public String mUserName; 91 public String mPassword; 92 String mDomain = null; 93 boolean mSentCommands; 94 boolean mIsIdle = false; 95 boolean mSsl = true; 96 public Context mContext; 97 public ContentResolver mContentResolver; 98 String[] mBindArguments = new String[2]; 99 InputStream mPendingPartInputStream = null; 100 private boolean mStop = false; 101 private Object mWaitTarget = new Object(); 102 103 public EasSyncService(Context _context, Mailbox _mailbox) { 104 super(_context, _mailbox); 105 mContext = _context; 106 mContentResolver = _context.getContentResolver(); 107 HostAuth ha = HostAuth.restoreHostAuthWithId(_context, mAccount.mHostAuthKeyRecv); 108 mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0; 109 } 110 111 private EasSyncService(String prefix) { 112 super(prefix); 113 } 114 115 public EasSyncService() { 116 this("EAS Validation"); 117 } 118 119 @Override 120 public void ping() { 121 userLog("We've been pinged!"); 122 synchronized (mWaitTarget) { 123 mWaitTarget.notify(); 124 } 125 } 126 127 @Override 128 public void stop() { 129 mStop = true; 130 } 131 132 public int getSyncStatus() { 133 return 0; 134 } 135 136 /* (non-Javadoc) 137 * @see com.android.exchange.SyncService#validateAccount(java.lang.String, java.lang.String, java.lang.String, int, boolean, android.content.Context) 138 */ 139 public void validateAccount(String hostAddress, String userName, String password, int port, 140 boolean ssl, Context context) throws MessagingException { 141 try { 142 if (Eas.USER_DEBUG) { 143 userLog("Testing EAS: " + hostAddress + ", " + userName + ", ssl = " + ssl); 144 } 145 EasSerializer s = new EasSerializer(); 146 s.start("FolderSync").start("FolderSyncKey").text("0").end("FolderSyncKey") 147 .end("FolderSync").end(); 148 EasSyncService svc = new EasSyncService("%TestAccount%"); 149 svc.mHostAddress = hostAddress; 150 svc.mUserName = userName; 151 svc.mPassword = password; 152 svc.mSsl = ssl; 153 HttpURLConnection uc = svc.sendEASPostCommand("FolderSync", s.toString()); 154 int code = uc.getResponseCode(); 155 userLog("Validation response code: " + code); 156 if (code == HttpURLConnection.HTTP_OK) { 157 // No exception means successful validation 158 userLog("Validation successful"); 159 return; 160 } 161 if (code == HttpURLConnection.HTTP_UNAUTHORIZED || 162 code == HttpURLConnection.HTTP_FORBIDDEN) { 163 userLog("Authentication failed"); 164 throw new AuthenticationFailedException("Validation failed"); 165 } else { 166 // TODO Need to catch other kinds of errors (e.g. policy) For now, report the code. 167 userLog("Validation failed, reporting I/O error: " + code); 168 throw new MessagingException(MessagingException.IOERROR); 169 } 170 } catch (IOException e) { 171 userLog("IOException caught, reporting I/O error: " + e.getMessage()); 172 throw new MessagingException(MessagingException.IOERROR); 173 } 174 175 } 176 177 178 @Override 179 public void loadAttachment(Attachment att, IEmailServiceCallback cb) { 180 // TODO Auto-generated method stub 181 } 182 183 @Override 184 public void reloadFolderList() { 185 // TODO Auto-generated method stub 186 } 187 188 @Override 189 public void startSync() { 190 // TODO Auto-generated method stub 191 } 192 193 @Override 194 public void stopSync() { 195 // TODO Auto-generated method stub 196 } 197 198 protected HttpURLConnection sendEASPostCommand(String cmd, String data) throws IOException { 199 HttpURLConnection uc = setupEASCommand("POST", cmd); 200 if (uc != null) { 201 uc.setRequestProperty("Content-Length", Integer.toString(data.length() + 2)); 202 OutputStreamWriter w = new OutputStreamWriter(uc.getOutputStream(), "UTF-8"); 203 w.write(data); 204 w.write("\r\n"); 205 w.flush(); 206 w.close(); 207 } 208 return uc; 209 } 210 211 static private final int CHUNK_SIZE = 16 * 1024; 212 213 protected void getAttachment(PartRequest req) throws IOException { 214 DefaultHttpClient client = new DefaultHttpClient(); 215 String us = makeUriString("GetAttachment", "&AttachmentName=" + req.att.mLocation); 216 HttpPost method = new HttpPost(URI.create(us)); 217 method.setHeader("Authorization", mAuthString); 218 219 HttpResponse res = client.execute(method); 220 int status = res.getStatusLine().getStatusCode(); 221 if (status == HttpURLConnection.HTTP_OK) { 222 HttpEntity e = res.getEntity(); 223 int len = (int)e.getContentLength(); 224 String type = e.getContentType().getValue(); 225 if (Eas.TEST_DEBUG) { 226 Log.v(TAG, "Attachment code: " + status + ", Length: " + len + ", Type: " + type); 227 } 228 InputStream is = res.getEntity().getContent(); 229 // TODO Use the request data, when it's defined. For now, stubbed out 230 File f = null; // Attachment.openAttachmentFile(req); 231 if (f != null) { 232 FileOutputStream os = new FileOutputStream(f); 233 if (len > 0) { 234 try { 235 mPendingPartRequest = req; 236 mPendingPartInputStream = is; 237 byte[] bytes = new byte[CHUNK_SIZE]; 238 int length = len; 239 while (len > 0) { 240 int n = (len > CHUNK_SIZE ? CHUNK_SIZE : len); 241 int read = is.read(bytes, 0, n); 242 os.write(bytes, 0, read); 243 len -= read; 244 if (req.handler != null) { 245 long pct = ((length - len) * 100 / length); 246 req.handler.sendEmptyMessage((int)pct); 247 } 248 } 249 } finally { 250 mPendingPartRequest = null; 251 mPendingPartInputStream = null; 252 } 253 } 254 os.flush(); 255 os.close(); 256 257 ContentValues cv = new ContentValues(); 258 cv.put(AttachmentColumns.CONTENT_URI, f.getAbsolutePath()); 259 cv.put(AttachmentColumns.MIME_TYPE, type); 260 req.att.update(mContext, cv); 261 // TODO Inform UI that we're done 262 } 263 } 264 } 265 266 private HttpURLConnection setupEASCommand(String method, String cmd) throws IOException { 267 return setupEASCommand(method, cmd, null); 268 } 269 270 private String makeUriString(String cmd, String extra) { 271 // Cache the authentication string and the command string 272 if (mDeviceId == null) 273 mDeviceId = "droidfu"; 274 String safeUserName = URLEncoder.encode(mUserName); 275 if (mAuthString == null) { 276 String cs = mUserName + ':' + mPassword; 277 mAuthString = "Basic " + Base64.encodeBytes(cs.getBytes()); 278 mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + "&DeviceType=" 279 + mDeviceType; 280 } 281 282 String us = (mSsl ? "https" : "http") + "://" + mHostAddress + 283 "/Microsoft-Server-ActiveSync"; 284 if (cmd != null) { 285 us += "?Cmd=" + cmd + mCmdString; 286 } 287 if (extra != null) { 288 us += extra; 289 } 290 return us; 291 } 292 293 private HttpURLConnection setupEASCommand(String method, String cmd, String extra) 294 throws IOException { 295 try { 296 String us = makeUriString(cmd, extra); 297 URL u = new URL(us); 298 HttpURLConnection uc = (HttpURLConnection)u.openConnection(); 299 HttpURLConnection.setFollowRedirects(true); 300 301 if (mSsl) { 302 ((HttpsURLConnection)uc).setHostnameVerifier(new AllowAllHostnameVerifier()); 303 } 304 305 uc.setConnectTimeout(10 * SECS); 306 uc.setReadTimeout(20 * MINS); 307 if (method.equals("POST")) { 308 uc.setDoOutput(true); 309 } 310 uc.setRequestMethod(method); 311 uc.setRequestProperty("Authorization", mAuthString); 312 313 if (extra == null) { 314 if (cmd != null && cmd.startsWith("SendMail&")) { 315 uc.setRequestProperty("Content-Type", "message/rfc822"); 316 } else { 317 uc.setRequestProperty("Content-Type", "application/vnd.ms-sync.wbxml"); 318 } 319 uc.setRequestProperty("MS-ASProtocolVersion", mProtocolVersion); 320 uc.setRequestProperty("Connection", "keep-alive"); 321 uc.setRequestProperty("User-Agent", mDeviceType + '/' + Eas.VERSION); 322 } else { 323 uc.setRequestProperty("Content-Length", "0"); 324 } 325 326 return uc; 327 } catch (MalformedURLException e) { 328 // TODO See if there is a better exception to throw here and below 329 throw new IOException(); 330 } catch (ProtocolException e) { 331 throw new IOException(); 332 } 333 } 334 335 String getTargetCollectionClassFromCursor(Cursor c) { 336 int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN); 337 if (type == Mailbox.TYPE_CONTACTS) { 338 return "Contacts"; 339 } else if (type == Mailbox.TYPE_CALENDAR) { 340 return "Calendar"; 341 } else { 342 return "Email"; 343 } 344 } 345 346 /** 347 * Performs FolderSync 348 * 349 * @throws IOException 350 * @throws EasParserException 351 */ 352 public void runMain() throws IOException, EasParserException { 353 try { 354 if (mAccount.mSyncKey == null) { 355 mAccount.mSyncKey = "0"; 356 userLog("Account syncKey RESET"); 357 mAccount.saveOrUpdate(mContext); 358 } 359 360 // When we first start up, change all ping mailboxes to push. 361 ContentValues cv = new ContentValues(); 362 cv.put(Mailbox.SYNC_FREQUENCY, Account.CHECK_INTERVAL_PUSH); 363 if (mContentResolver.update(Mailbox.CONTENT_URI, cv, 364 WHERE_SYNC_FREQUENCY_PING, null) > 0) { 365 SyncManager.kick(); 366 } 367 368 userLog("Account syncKey: " + mAccount.mSyncKey); 369 HttpURLConnection uc = setupEASCommand("OPTIONS", null); 370 if (uc != null) { 371 int code = uc.getResponseCode(); 372 userLog("OPTIONS response: " + code); 373 if (code == HttpURLConnection.HTTP_OK) { 374 mVersions = uc.getHeaderField("ms-asprotocolversions"); 375 if (mVersions != null) { 376 if (mVersions.contains("12.0")) { 377 mProtocolVersion = "12.0"; 378 } 379 // TODO We only do 2.5 at the moment; add 'else' above when fixed 380 mProtocolVersion = "2.5"; 381 userLog(mVersions); 382 } else { 383 throw new IOException(); 384 } 385 386 while (!mStop) { 387 EasSerializer s = new EasSerializer(); 388 s.start("FolderSync").start("FolderSyncKey").text(mAccount.mSyncKey).end( 389 "FolderSyncKey").end("FolderSync").end(); 390 uc = sendEASPostCommand("FolderSync", s.toString()); 391 code = uc.getResponseCode(); 392 if (code == HttpURLConnection.HTTP_OK) { 393 String encoding = uc.getHeaderField("Transfer-Encoding"); 394 if (encoding == null) { 395 int len = uc.getHeaderFieldInt("Content-Length", 0); 396 if (len > 0) { 397 InputStream is = uc.getInputStream(); 398 // Returns true if we need to sync again 399 if (new EasFolderSyncParser(is, this).parse()) { 400 continue; 401 } 402 } 403 } else if (encoding.equalsIgnoreCase("chunked")) { 404 // TODO We don't handle this yet 405 } 406 } else { 407 userLog("FolderSync response error: " + code); 408 } 409 410 // Wait for push notifications. 411 try { 412 runPingLoop(); 413 } catch (StaleFolderListException e) { 414 // We break out if we get told about a stale folder list 415 userLog("Ping interrupted; folder list requires sync..."); 416 } 417 } 418 } 419 } 420 } catch (MalformedURLException e) { 421 throw new IOException(); 422 } 423 } 424 425 void runPingLoop() throws IOException, StaleFolderListException { 426 // Do push for all sync services here 427 long endTime = System.currentTimeMillis() + (30*MINS); 428 429 while (System.currentTimeMillis() < endTime) { 430 // Count of pushable mailboxes 431 int pushCount = 0; 432 // Count of mailboxes that can be pushed right now 433 int canPushCount = 0; 434 EasSerializer s = new EasSerializer(); 435 HttpURLConnection uc; 436 int code; 437 Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, 438 MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId + " and " + SYNC_FREQUENCY_PING, 439 null, null); 440 441 try { 442 // Loop through our pushed boxes seeing what is available to push 443 while (c.moveToNext()) { 444 pushCount++; 445 // Two requirements for push: 446 // 1) SyncManager tells us the mailbox is syncable (not running, not stopped) 447 // 2) The syncKey isn't "0" (i.e. it's synced at least once) 448 if (SyncManager.canSync(c.getLong(Mailbox.CONTENT_ID_COLUMN))) { 449 String syncKey = c.getString(Mailbox.CONTENT_SYNC_KEY_COLUMN); 450 if (syncKey == null || syncKey.equals("0")) { 451 continue; 452 } 453 if (canPushCount++ == 0) { 454 // Initialize the Ping command 455 s.start("Ping").data("HeartbeatInterval", "900").start("PingFolders"); 456 } 457 // When we're ready for Calendar/Contacts, we will check folder type 458 // TODO Save Calendar and Contacts!! Mark as not visible! 459 String folderClass = getTargetCollectionClassFromCursor(c); 460 s.start("PingFolder") 461 .data("PingId", c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN)) 462 .data("PingClass", folderClass) 463 .end("PingFolder"); 464 } 465 } 466 } finally { 467 c.close(); 468 } 469 470 if (canPushCount > 0) { 471 // If we have some number that are ready for push, send Ping to the server 472 s.end("PingFolders").end("Ping").end(); 473 uc = sendEASPostCommand("Ping", s.toString()); 474 userLog("Sending ping, timeout: " + uc.getReadTimeout() / 1000 + "s"); 475 code = uc.getResponseCode(); 476 userLog("Ping response: " + code); 477 if (code == HttpURLConnection.HTTP_OK) { 478 String encoding = uc.getHeaderField("Transfer-Encoding"); 479 if (encoding == null) { 480 int len = uc.getHeaderFieldInt("Content-Length", 0); 481 if (len > 0) { 482 parsePingResult(uc, mContentResolver); 483 } else { 484 // This implies a connection issue that we can't handle 485 throw new IOException(); 486 } 487 } else { 488 // It shouldn't be possible for EAS server to send chunked data here 489 throw new IOException(); 490 } 491 } else if (code == HttpURLConnection.HTTP_UNAUTHORIZED || 492 code == HttpURLConnection.HTTP_FORBIDDEN) { 493 mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE; 494 userLog("Authorization error during Ping: " + code); 495 throw new IOException(); 496 } 497 } else if (pushCount > 0) { 498 // If we want to Ping, but can't just yet, wait 10 seconds and try again 499 sleep(10*SECS); 500 } else { 501 // We've got nothing to do, so let's hang out for a while 502 sleep(10*MINS); 503 } 504 } 505 } 506 507 void sleep(long ms) { 508 try { 509 Thread.sleep(ms); 510 } catch (InterruptedException e) { 511 // Doesn't matter whether we stop early; it's the thought that counts 512 } 513 } 514 515 void parsePingResult(HttpURLConnection uc, ContentResolver cr) 516 throws IOException, StaleFolderListException { 517 EasPingParser pp = new EasPingParser(uc.getInputStream(), this); 518 if (pp.parse()) { 519 // True indicates some mailboxes need syncing... 520 // syncList has the serverId's of the mailboxes... 521 mBindArguments[0] = Long.toString(mAccount.mId); 522 ArrayList<String> syncList = pp.getSyncList(); 523 for (String serverId: syncList) { 524 mBindArguments[1] = serverId; 525 Cursor c = cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, 526 WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null); 527 try { 528 if (c.moveToFirst()) { 529 SyncManager.startManualSync(c.getLong(Mailbox.CONTENT_ID_COLUMN)); 530 } 531 } finally { 532 c.close(); 533 } 534 } 535 } 536 } 537 538 ByteArrayInputStream readResponse(HttpURLConnection uc) throws IOException { 539 String encoding = uc.getHeaderField("Transfer-Encoding"); 540 if (encoding == null) { 541 int len = uc.getHeaderFieldInt("Content-Length", 0); 542 if (len > 0) { 543 InputStream in = uc.getInputStream(); 544 byte[] bytes = new byte[len]; 545 int remain = len; 546 int offs = 0; 547 while (remain > 0) { 548 int read = in.read(bytes, offs, remain); 549 remain -= read; 550 offs += read; 551 } 552 return new ByteArrayInputStream(bytes); 553 } 554 } else if (encoding.equalsIgnoreCase("chunked")) { 555 // TODO We don't handle this yet 556 return null; 557 } 558 return null; 559 } 560 561 String readResponseString(HttpURLConnection uc) throws IOException { 562 String encoding = uc.getHeaderField("Transfer-Encoding"); 563 if (encoding == null) { 564 int len = uc.getHeaderFieldInt("Content-Length", 0); 565 if (len > 0) { 566 InputStream in = uc.getInputStream(); 567 byte[] bytes = new byte[len]; 568 int remain = len; 569 int offs = 0; 570 while (remain > 0) { 571 int read = in.read(bytes, offs, remain); 572 remain -= read; 573 offs += read; 574 } 575 return new String(bytes); 576 } 577 } else if (encoding.equalsIgnoreCase("chunked")) { 578 // TODO We don't handle this yet 579 return null; 580 } 581 return null; 582 } 583 584 /** 585 * EAS requires a unique device id, so that sync is possible from a variety of different 586 * devices (e.g. the syncKey is specific to a device) If we're on an emulator or some other 587 * device that doesn't provide one, we can create it as droid<n> where <n> is system time. 588 * This would work on a real device as well, but it would be better to use the "real" id if 589 * it's available 590 */ 591 private String getSimulatedDeviceId() { 592 try { 593 File f = mContext.getFileStreamPath("deviceName"); 594 BufferedReader rdr = null; 595 String id; 596 if (f.exists() && f.canRead()) { 597 rdr = new BufferedReader(new FileReader(f)); 598 id = rdr.readLine(); 599 rdr.close(); 600 return id; 601 } else if (f.createNewFile()) { 602 BufferedWriter w = new BufferedWriter(new FileWriter(f)); 603 id = "droid" + System.currentTimeMillis(); 604 w.write(id); 605 w.close(); 606 } 607 } catch (FileNotFoundException e) { 608 // We'll just use the default below 609 } catch (IOException e) { 610 // We'll just use the default below 611 } 612 return "droid0"; 613 } 614 615 /** 616 * Common code to sync E+PIM data 617 * 618 * @param target, an EasMailbox, EasContacts, or EasCalendar object 619 */ 620 public void sync(EasSyncAdapter target) throws IOException { 621 mTarget = target; 622 Mailbox mailbox = target.mMailbox; 623 624 boolean moreAvailable = true; 625 while (!mStop && moreAvailable) { 626 runAwake(); 627 waitForConnectivity(); 628 629 EasSerializer s = new EasSerializer(); 630 if (mailbox.mSyncKey == null) { 631 userLog("Mailbox syncKey RESET"); 632 mailbox.mSyncKey = "0"; 633 mailbox.mSyncFrequency = Account.CHECK_INTERVAL_PUSH; 634 } 635 String className = target.getCollectionName(); 636 userLog("Sending " + className + " syncKey: " + mailbox.mSyncKey); 637 s.start("Sync") 638 .start("Collections") 639 .start("Collection") 640 .data("Class", className) 641 .data("SyncKey", mailbox.mSyncKey) 642 .data("CollectionId", mailbox.mServerId) 643 .tag("DeletesAsMoves"); 644 645 // EAS doesn't like GetChanges if the syncKey is "0"; not documented 646 if (!mailbox.mSyncKey.equals("0")) { 647 s.tag("GetChanges"); 648 } 649 s.data("WindowSize", WINDOW_SIZE); 650 boolean options = false; 651 if (!className.equals("Contacts")) { 652 options = true; 653 // Set the lookback appropriately (EAS calls this a "filter") 654 String filter = Eas.FILTER_1_WEEK; 655 switch (mAccount.mSyncLookback) { 656 case com.android.email.Account.SYNC_WINDOW_1_DAY: { 657 filter = Eas.FILTER_1_DAY; 658 break; 659 } 660 case com.android.email.Account.SYNC_WINDOW_3_DAYS: { 661 filter = Eas.FILTER_3_DAYS; 662 break; 663 } 664 case com.android.email.Account.SYNC_WINDOW_1_WEEK: { 665 filter = Eas.FILTER_1_WEEK; 666 break; 667 } 668 case com.android.email.Account.SYNC_WINDOW_2_WEEKS: { 669 filter = Eas.FILTER_2_WEEKS; 670 break; 671 } 672 case com.android.email.Account.SYNC_WINDOW_1_MONTH: { 673 filter = Eas.FILTER_1_MONTH; 674 break; 675 } 676 case com.android.email.Account.SYNC_WINDOW_ALL: { 677 filter = Eas.FILTER_ALL; 678 break; 679 } 680 } 681 s.start("Options") 682 .data("FilterType", filter); 683 } 684 if (mProtocolVersion.equals("12.0")) { 685 if (!options) { 686 options = true; 687 s.start("Options"); 688 s.start("BodyPreference") 689 // Plain text to start 690 .data("BodyPreferenceType", Eas.BODY_PREFERENCE_TEXT) 691 .data("BodyPreferenceTruncationSize", Eas.DEFAULT_BODY_TRUNCATION_SIZE) 692 .end("BodyPreference"); 693 } 694 } 695 if (options) { 696 s.end("Options"); 697 } 698 699 // Send our changes up to the server 700 target.sendLocalChanges(s, this); 701 702 s.end("Collection").end("Collections").end("Sync").end(); 703 HttpURLConnection uc = sendEASPostCommand("Sync", s.toString()); 704 int code = uc.getResponseCode(); 705 if (code == HttpURLConnection.HTTP_OK) { 706 ByteArrayInputStream is = readResponse(uc); 707 if (is != null) { 708 moreAvailable = target.parse(is, this); 709 target.cleanup(this); 710 } 711 } else { 712 userLog("Sync response error: " + code); 713 if (code == HttpURLConnection.HTTP_UNAUTHORIZED || 714 code == HttpURLConnection.HTTP_FORBIDDEN) { 715 mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE; 716 } 717 return; 718 } 719 } 720 } 721 722 /* (non-Javadoc) 723 * @see java.lang.Runnable#run() 724 */ 725 public void run() { 726 mThread = Thread.currentThread(); 727 TAG = mThread.getName(); 728 mDeviceId = android.provider.Settings.System.getString(mContext.getContentResolver(), 729 android.provider.Settings.System.ANDROID_ID); 730 // Generate a device id if we don't have one 731 if (mDeviceId == null) { 732 mDeviceId = getSimulatedDeviceId(); 733 } 734 HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv); 735 mHostAddress = ha.mAddress; 736 mUserName = ha.mLogin; 737 mPassword = ha.mPassword; 738 739 try { 740 if (mMailbox.mServerId.equals("_main")) { 741 runMain(); 742 } else { 743 EasSyncAdapter target; 744 if (mMailbox.mType == Mailbox.TYPE_CONTACTS) 745 target = new EasContactsSyncAdapter(mMailbox); 746 else { 747 target = new EasEmailSyncAdapter(mMailbox); 748 } 749 // We loop here because someone might have put a request in while we were syncing 750 // and we've missed that opportunity... 751 do { 752 if (mRequestTime != 0) { 753 userLog("Looping for user request..."); 754 mRequestTime = 0; 755 } 756 sync(target); 757 } while (mRequestTime != 0); 758 } 759 mExitStatus = EXIT_DONE; 760 } catch (IOException e) { 761 userLog("Caught IOException"); 762 mExitStatus = EXIT_IO_ERROR; 763 } catch (Exception e) { 764 e.printStackTrace(); 765 } finally { 766 userLog(mMailbox.mDisplayName + ": sync finished"); 767 SyncManager.done(this); 768 } 769 } 770} 771