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 android.content.ContentResolver; 21 import android.content.ContentUris; 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.database.ContentObserver; 26 import android.database.Cursor; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.IBinder; 31 import android.os.RemoteException; 32 import android.provider.CalendarContract; 33 import android.provider.CalendarContract.Calendars; 34 import android.provider.CalendarContract.Events; 35 36 import com.android.emailcommon.Api; 37 import com.android.emailcommon.provider.Account; 38 import com.android.emailcommon.provider.EmailContent.Attachment; 39 import com.android.emailcommon.provider.EmailContent.MailboxColumns; 40 import com.android.emailcommon.provider.EmailContent.Message; 41 import com.android.emailcommon.provider.EmailContent.SyncColumns; 42 import com.android.emailcommon.provider.HostAuth; 43 import com.android.emailcommon.provider.Mailbox; 44 import com.android.emailcommon.provider.MailboxUtilities; 45 import com.android.emailcommon.provider.ProviderUnavailableException; 46 import com.android.emailcommon.service.AccountServiceProxy; 47 import com.android.emailcommon.service.IEmailService; 48 import com.android.emailcommon.service.IEmailServiceCallback; 49 import com.android.emailcommon.service.IEmailServiceCallback.Stub; 50 import com.android.emailcommon.service.SearchParams; 51 import com.android.emailsync.AbstractSyncService; 52 import com.android.emailsync.PartRequest; 53 import com.android.emailsync.SyncManager; 54 import com.android.exchange.adapter.Search; 55 import com.android.exchange.utility.FileLogger; 56 import com.android.mail.providers.UIProvider.AccountCapabilities; 57 import com.android.mail.utils.LogUtils; 58 59 import java.util.concurrent.ConcurrentHashMap; 60 61 /** 62 * The ExchangeService handles all aspects of starting, maintaining, and stopping the various sync 63 * adapters used by Exchange. However, it is capable of handing any kind of email sync, and it 64 * would be appropriate to use for IMAP push, when that functionality is added to the Email 65 * application. 66 * 67 * The Email application communicates with EAS sync adapters via ExchangeService's binder interface, 68 * which exposes UI-related functionality to the application (see the definitions below) 69 * 70 * ExchangeService uses ContentObservers to detect changes to accounts, mailboxes, and messages in 71 * order to maintain proper 2-way syncing of data. (More documentation to follow) 72 * 73 */ 74 public class ExchangeService extends SyncManager { 75 76 private static final String TAG = Eas.LOG_TAG; 77 78 private static final String WHERE_PUSH_OR_PING_NOT_ACCOUNT_MAILBOX = 79 MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.TYPE + "!=" + 80 Mailbox.TYPE_EAS_ACCOUNT_MAILBOX + " and " + MailboxColumns.SYNC_INTERVAL + 81 " IN (" + Mailbox.CHECK_INTERVAL_PING + ',' + Mailbox.CHECK_INTERVAL_PUSH + ')'; 82 private static final String WHERE_MAILBOX_KEY = Message.MAILBOX_KEY + "=?"; 83 private static final String WHERE_CALENDAR_ID = Events.CALENDAR_ID + "=?"; 84 private static final String ACCOUNT_KEY_IN = MailboxColumns.ACCOUNT_KEY + " in ("; 85 86 // Offsets into the syncStatus data for EAS that indicate type, exit status, and change count 87 // The format is S<type_char>:<exit_char>:<change_count> 88 public static final int STATUS_TYPE_CHAR = 1; 89 public static final int STATUS_EXIT_CHAR = 3; 90 public static final int STATUS_CHANGE_COUNT_OFFSET = 5; 91 92 private static final int EAS_12_CAPABILITIES = 93 AccountCapabilities.SYNCABLE_FOLDERS | 94 AccountCapabilities.SERVER_SEARCH | 95 AccountCapabilities.FOLDER_SERVER_SEARCH | 96 AccountCapabilities.SMART_REPLY | 97 AccountCapabilities.SERVER_SEARCH | 98 AccountCapabilities.UNDO; 99 100 private static final int EAS_2_CAPABILITIES = 101 AccountCapabilities.SYNCABLE_FOLDERS | 102 AccountCapabilities.SMART_REPLY | 103 AccountCapabilities.UNDO; 104 105 // We synchronize on this for all actions affecting the service and error maps 106 private static final Object sSyncLock = new Object(); 107 private String mEasAccountSelector; 108 109 // Concurrent because CalendarSyncAdapter can modify the map during a wipe 110 private final ConcurrentHashMap<Long, CalendarObserver> mCalendarObservers = 111 new ConcurrentHashMap<Long, CalendarObserver>(); 112 113 private final Intent mIntent = new Intent(Eas.EXCHANGE_SERVICE_INTENT_ACTION); 114 115 /** 116 * Create our EmailService implementation here. 117 */ 118 private final IEmailService.Stub mBinder = new IEmailService.Stub() { 119 120 @Override 121 public int getApiLevel() { 122 return Api.LEVEL; 123 } 124 125 @Override 126 public Bundle validate(HostAuth hostAuth) throws RemoteException { 127 return AbstractSyncService.validate(EasSyncService.class, 128 hostAuth, ExchangeService.this); 129 } 130 131 @Override 132 public Bundle autoDiscover(String userName, String password) throws RemoteException { 133 HostAuth hostAuth = new HostAuth(); 134 hostAuth.mLogin = userName; 135 hostAuth.mPassword = password; 136 hostAuth.mFlags = HostAuth.FLAG_AUTHENTICATE | HostAuth.FLAG_SSL; 137 hostAuth.mPort = 443; 138 return new EasSyncService().tryAutodiscover(ExchangeService.this, hostAuth); 139 } 140 141 /** 142 * This is the remote call from the Email app, currently unused. 143 * TODO: remove this when it's been deleted from IEmailService.aidl. 144 */ 145 @Deprecated 146 @Override 147 public void startSync(long mailboxId, boolean userRequest, int deltaMessageCount) 148 throws RemoteException { 149 SyncManager exchangeService = INSTANCE; 150 if (exchangeService == null) return; 151 checkExchangeServiceServiceRunning(); 152 Mailbox m = Mailbox.restoreMailboxWithId(exchangeService, mailboxId); 153 if (m == null) return; 154 Account acct = Account.restoreAccountWithId(exchangeService, m.mAccountKey); 155 if (acct == null) return; 156 // If this is a user request and we're being held, release the hold; this allows us to 157 // try again (the hold might have been specific to this account and released already) 158 if (userRequest) { 159 if (onSyncDisabledHold(acct)) { 160 releaseSyncHolds(exchangeService, AbstractSyncService.EXIT_ACCESS_DENIED, acct); 161 log("User requested sync of account in sync disabled hold; releasing"); 162 } else if (onSecurityHold(acct)) { 163 releaseSyncHolds(exchangeService, AbstractSyncService.EXIT_SECURITY_FAILURE, 164 acct); 165 log("User requested sync of account in security hold; releasing"); 166 } 167 if (sConnectivityHold) { 168 return; 169 } 170 } 171 if (m.mType == Mailbox.TYPE_OUTBOX) { 172 // We're using SERVER_ID to indicate an error condition (it has no other use for 173 // sent mail) Upon request to sync the Outbox, we clear this so that all messages 174 // are candidates for sending. 175 ContentValues cv = new ContentValues(); 176 cv.put(SyncColumns.SERVER_ID, 0); 177 exchangeService.getContentResolver().update(Message.CONTENT_URI, 178 cv, WHERE_MAILBOX_KEY, new String[] {Long.toString(mailboxId)}); 179 // Clear the error state; the Outbox sync will be started from checkMailboxes 180 exchangeService.mSyncErrorMap.remove(mailboxId); 181 kick("start outbox"); 182 // Outbox can't be synced in EAS 183 return; 184 } else if (!isSyncable(m)) { 185 return; 186 } 187 startManualSync(mailboxId, userRequest ? ExchangeService.SYNC_UI_REQUEST : 188 ExchangeService.SYNC_SERVICE_START_SYNC, null); 189 } 190 191 @Override 192 public void stopSync(long mailboxId) throws RemoteException { 193 stopManualSync(mailboxId); 194 } 195 196 @Override 197 public void loadAttachment(final IEmailServiceCallback callback, final long attachmentId, 198 final boolean background) throws RemoteException { 199 Attachment att = Attachment.restoreAttachmentWithId(ExchangeService.this, attachmentId); 200 log("loadAttachment " + attachmentId + ": " + att.mFileName); 201 sendMessageRequest(new PartRequest(att, null, null)); 202 } 203 204 @Override 205 public void updateFolderList(long accountId) throws RemoteException { 206 reloadFolderList(ExchangeService.this, accountId, false); 207 } 208 209 @Override 210 public void hostChanged(long accountId) throws RemoteException { 211 SyncManager exchangeService = INSTANCE; 212 if (exchangeService == null) return; 213 ConcurrentHashMap<Long, SyncError> syncErrorMap = exchangeService.mSyncErrorMap; 214 // Go through the various error mailboxes 215 for (long mailboxId: syncErrorMap.keySet()) { 216 SyncError error = syncErrorMap.get(mailboxId); 217 // If it's a login failure, look a little harder 218 Mailbox m = Mailbox.restoreMailboxWithId(exchangeService, mailboxId); 219 // If it's for the account whose host has changed, clear the error 220 // If the mailbox is no longer around, remove the entry in the map 221 if (m == null) { 222 syncErrorMap.remove(mailboxId); 223 } else if (error != null && m.mAccountKey == accountId) { 224 error.fatal = false; 225 error.holdEndTime = 0; 226 } 227 } 228 // Stop any running syncs 229 exchangeService.stopAccountSyncs(accountId, true); 230 // Kick ExchangeService 231 kick("host changed"); 232 } 233 234 @Override 235 public void setLogging(int flags) throws RemoteException { 236 // Protocol logging 237 Eas.setUserDebug(flags); 238 // Sync logging 239 setUserDebug(flags); 240 } 241 242 @Override 243 public void sendMeetingResponse(long messageId, int response) throws RemoteException { 244 sendMessageRequest(new MeetingResponseRequest(messageId, response)); 245 } 246 247 @Override 248 public void loadMore(long messageId) throws RemoteException { 249 } 250 251 // The following three methods are not implemented in this version 252 @Override 253 public boolean createFolder(long accountId, String name) throws RemoteException { 254 return false; 255 } 256 257 @Override 258 public boolean deleteFolder(long accountId, String name) throws RemoteException { 259 return false; 260 } 261 262 @Override 263 public boolean renameFolder(long accountId, String oldName, String newName) 264 throws RemoteException { 265 return false; 266 } 267 268 /** 269 * Delete PIM (calendar, contacts) data for the specified account 270 * 271 * @param emailAddress the email address for the account whose data should be deleted 272 * @throws RemoteException 273 */ 274 @Override 275 public void deleteAccountPIMData(final String emailAddress) throws RemoteException { 276 // ExchangeService is deprecated so I am deleting rather than fixing this function. 277 } 278 279 @Override 280 public int searchMessages(long accountId, SearchParams searchParams, long destMailboxId) { 281 SyncManager exchangeService = INSTANCE; 282 if (exchangeService == null) return 0; 283 return Search.searchMessages(exchangeService, accountId, searchParams, 284 destMailboxId); 285 } 286 287 @Override 288 public void sendMail(long accountId) throws RemoteException { 289 } 290 291 @Override 292 public int getCapabilities(Account acct) throws RemoteException { 293 String easVersion = acct.mProtocolVersion; 294 Double easVersionDouble = 2.5D; 295 if (easVersion != null) { 296 try { 297 easVersionDouble = Double.parseDouble(easVersion); 298 } catch (NumberFormatException e) { 299 // Stick with 2.5 300 } 301 } 302 if (easVersionDouble >= 12.0D) { 303 return EAS_12_CAPABILITIES; 304 } else { 305 return EAS_2_CAPABILITIES; 306 } 307 } 308 309 @Override 310 public void serviceUpdated(String emailAddress) throws RemoteException { 311 // Not required for EAS 312 } 313 }; 314 315 /** 316 * Return a list of all Accounts in EmailProvider. Because the result of this call may be used 317 * in account reconciliation, an exception is thrown if the result cannot be guaranteed accurate 318 * @param context the caller's context 319 * @param accounts a list that Accounts will be added into 320 * @return the list of Accounts 321 * @throws ProviderUnavailableException if the list of Accounts cannot be guaranteed valid 322 */ 323 @Override 324 public AccountList collectAccounts(Context context, AccountList accounts) { 325 ContentResolver resolver = context.getContentResolver(); 326 Cursor c = resolver.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null, 327 null); 328 // We must throw here; callers might use the information we provide for reconciliation, etc. 329 if (c == null) throw new ProviderUnavailableException(); 330 try { 331 ContentValues cv = new ContentValues(); 332 while (c.moveToNext()) { 333 long hostAuthId = c.getLong(Account.CONTENT_HOST_AUTH_KEY_RECV_COLUMN); 334 if (hostAuthId > 0) { 335 HostAuth ha = HostAuth.restoreHostAuthWithId(context, hostAuthId); 336 if (ha != null && ha.mProtocol.equals(Eas.PROTOCOL)) { 337 Account account = new Account(); 338 account.restore(c); 339 // Cache the HostAuth 340 account.mHostAuthRecv = ha; 341 accounts.add(account); 342 // Fixup flags for inbox (should accept moved mail) 343 Mailbox inbox = Mailbox.restoreMailboxOfType(context, account.mId, 344 Mailbox.TYPE_INBOX); 345 if (inbox != null && 346 ((inbox.mFlags & Mailbox.FLAG_ACCEPTS_MOVED_MAIL) == 0)) { 347 cv.put(MailboxColumns.FLAGS, 348 inbox.mFlags | Mailbox.FLAG_ACCEPTS_MOVED_MAIL); 349 resolver.update( 350 ContentUris.withAppendedId(Mailbox.CONTENT_URI, inbox.mId), cv, 351 null, null); 352 } 353 } 354 } 355 } 356 } finally { 357 c.close(); 358 } 359 return accounts; 360 } 361 362 public static boolean onSecurityHold(Account account) { 363 return (account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0; 364 } 365 366 private static boolean onSyncDisabledHold(Account account) { 367 return (account.mFlags & Account.FLAGS_SYNC_DISABLED) != 0; 368 } 369 370 private static Uri eventsAsSyncAdapter(final Uri uri, final String account, 371 final String accountType) { 372 return uri.buildUpon() 373 .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") 374 .appendQueryParameter(Calendars.ACCOUNT_NAME, account) 375 .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); 376 } 377 378 /** 379 * Unregister all CalendarObserver's 380 */ 381 static public void unregisterCalendarObservers() { 382 ExchangeService exchangeService = (ExchangeService)INSTANCE; 383 if (exchangeService == null) return; 384 ContentResolver resolver = exchangeService.mResolver; 385 for (CalendarObserver observer: exchangeService.mCalendarObservers.values()) { 386 resolver.unregisterContentObserver(observer); 387 } 388 exchangeService.mCalendarObservers.clear(); 389 } 390 391 private class CalendarObserver extends ContentObserver { 392 long mAccountId; 393 long mCalendarId; 394 long mSyncEvents; 395 String mAccountName; 396 397 public CalendarObserver(Handler handler, Account account) { 398 super(handler); 399 mAccountId = account.mId; 400 mAccountName = account.mEmailAddress; 401 402 // Find the Calendar for this account 403 Cursor c = mResolver.query(Calendars.CONTENT_URI, 404 new String[] {Calendars._ID, Calendars.SYNC_EVENTS}, 405 CALENDAR_SELECTION, 406 new String[] {account.mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE}, 407 null); 408 if (c != null) { 409 // Save its id and its sync events status 410 try { 411 if (c.moveToFirst()) { 412 mCalendarId = c.getLong(0); 413 mSyncEvents = c.getLong(1); 414 } 415 } finally { 416 c.close(); 417 } 418 } 419 } 420 421 private void onChangeInBackground() { 422 try { 423 Cursor c = mResolver.query(Calendars.CONTENT_URI, 424 new String[] {Calendars.SYNC_EVENTS}, Calendars._ID + "=?", 425 new String[] {Long.toString(mCalendarId)}, null); 426 if (c == null) return; 427 // Get its sync events; if it's changed, we've got work to do 428 try { 429 if (c.moveToFirst()) { 430 long newSyncEvents = c.getLong(0); 431 if (newSyncEvents != mSyncEvents) { 432 log("_sync_events changed for calendar in " + mAccountName); 433 Mailbox mailbox = Mailbox.restoreMailboxOfType(INSTANCE, 434 mAccountId, Mailbox.TYPE_CALENDAR); 435 // Sanity check for mailbox deletion 436 if (mailbox == null) return; 437 ContentValues cv = new ContentValues(); 438 if (newSyncEvents == 0) { 439 // When sync is disabled, we're supposed to delete 440 // all events in the calendar 441 log("Deleting events and setting syncKey to 0 for " + 442 mAccountName); 443 // First, stop any sync that's ongoing 444 stopManualSync(mailbox.mId); 445 // Set the syncKey to 0 (reset) 446 EasSyncService service = 447 EasSyncService.getServiceForMailbox( 448 INSTANCE, mailbox); 449 450 // CalendarSyncAdapter is gone, and this class is deprecated. 451 // Just leaving this commented out code here for reference: 452 // Reset the sync key locally and stop syncing 453 // CalendarSyncAdapter adapter = 454 // new CalendarSyncAdapter(service); 455 // try { 456 // adapter.setSyncKey("0", false); 457 // } catch (IOException e) { 458 // // The provider can't be reached; nothing to be done 459 // } 460 461 cv.put(Mailbox.SYNC_KEY, "0"); 462 cv.put(Mailbox.SYNC_INTERVAL, 463 Mailbox.CHECK_INTERVAL_NEVER); 464 mResolver.update(ContentUris.withAppendedId( 465 Mailbox.CONTENT_URI, mailbox.mId), cv, null, 466 null); 467 // Delete all events using the sync adapter 468 // parameter so that the deletion is only local 469 Uri eventsAsSyncAdapter = eventsAsSyncAdapter(Events.CONTENT_URI, 470 mAccountName, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE); 471 mResolver.delete(eventsAsSyncAdapter, WHERE_CALENDAR_ID, 472 new String[] {Long.toString(mCalendarId)}); 473 } else { 474 // Make this a push mailbox and kick; this will start 475 // a resync of the Calendar; the account mailbox will 476 // ping on this during the next cycle of the ping loop 477 cv.put(Mailbox.SYNC_INTERVAL, 478 Mailbox.CHECK_INTERVAL_PUSH); 479 mResolver.update(ContentUris.withAppendedId( 480 Mailbox.CONTENT_URI, mailbox.mId), cv, null, 481 null); 482 kick("calendar sync changed"); 483 } 484 485 // Save away the new value 486 mSyncEvents = newSyncEvents; 487 } 488 } 489 } finally { 490 c.close(); 491 } 492 } catch (ProviderUnavailableException e) { 493 LogUtils.w(TAG, "Observer failed; provider unavailable"); 494 } 495 } 496 497 498 @Override 499 public synchronized void onChange(boolean selfChange) { 500 // See if the user has changed syncing of our calendar 501 if (!selfChange) { 502 new Thread(new Runnable() { 503 @Override 504 public void run() { 505 onChangeInBackground(); 506 } 507 }, "Calendar Observer").start(); 508 } 509 } 510 } 511 512 /** 513 * Blocking call to the account reconciler 514 */ 515 @Override 516 public void runAccountReconcilerSync(Context context) { 517 alwaysLog("Reconciling accounts..."); 518 new AccountServiceProxy(context).reconcileAccounts( 519 Eas.PROTOCOL, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE); 520 } 521 522 public static void log(String str) { 523 log(TAG, str); 524 } 525 526 public static void log(String tag, String str) { 527 if (Eas.USER_LOG) { 528 LogUtils.d(tag, str); 529 if (Eas.FILE_LOG) { 530 FileLogger.log(tag, str); 531 } 532 } 533 } 534 535 public static void alwaysLog(String str) { 536 if (!Eas.USER_LOG) { 537 LogUtils.d(TAG, str); 538 } else { 539 log(str); 540 } 541 } 542 543 /** 544 * EAS requires a unique device id, so that sync is possible from a variety of different 545 * devices (e.g. the syncKey is specific to a device) If we're on an emulator or some other 546 * device that doesn't provide one, we can create it as "device". 547 * This would work on a real device as well, but it would be better to use the "real" id if 548 * it's available 549 */ 550 static public String getDeviceId(Context context) { 551 if (sDeviceId == null) { 552 sDeviceId = new AccountServiceProxy(context).getDeviceId(); 553 alwaysLog("Received deviceId from Email app: " + sDeviceId); 554 } 555 return sDeviceId; 556 } 557 558 @Override 559 public IBinder onBind(Intent arg0) { 560 return mBinder; 561 } 562 563 static private void reloadFolderListFailed(long accountId) { 564 565 } 566 567 static public void reloadFolderList(Context context, long accountId, boolean force) { 568 SyncManager exchangeService = INSTANCE; 569 if (exchangeService == null) return; 570 Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI, 571 Mailbox.CONTENT_PROJECTION, MailboxColumns.ACCOUNT_KEY + "=? AND " + 572 MailboxColumns.TYPE + "=?", 573 new String[] {Long.toString(accountId), 574 Long.toString(Mailbox.TYPE_EAS_ACCOUNT_MAILBOX)}, null); 575 try { 576 if (c.moveToFirst()) { 577 synchronized(sSyncLock) { 578 Mailbox mailbox = new Mailbox(); 579 mailbox.restore(c); 580 Account acct = Account.restoreAccountWithId(context, accountId); 581 if (acct == null) { 582 reloadFolderListFailed(accountId); 583 return; 584 } 585 String syncKey = acct.mSyncKey; 586 // No need to reload the list if we don't have one 587 if (!force && (syncKey == null || syncKey.equals("0"))) { 588 reloadFolderListFailed(accountId); 589 return; 590 } 591 592 // Change all ping/push boxes to push/hold 593 ContentValues cv = new ContentValues(); 594 cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH_HOLD); 595 context.getContentResolver().update(Mailbox.CONTENT_URI, cv, 596 WHERE_PUSH_OR_PING_NOT_ACCOUNT_MAILBOX, 597 new String[] {Long.toString(accountId)}); 598 log("Set push/ping boxes to push/hold"); 599 600 long id = mailbox.mId; 601 AbstractSyncService svc = exchangeService.mServiceMap.get(id); 602 // Tell the service we're done 603 if (svc != null) { 604 synchronized (svc.getSynchronizer()) { 605 svc.stop(); 606 // Interrupt the thread so that it can stop 607 Thread thread = svc.mThread; 608 if (thread != null) { 609 thread.setName(thread.getName() + " (Stopped)"); 610 thread.interrupt(); 611 } 612 } 613 // Abandon the service 614 exchangeService.releaseMailbox(id); 615 // And have it start naturally 616 kick("reload folder list"); 617 } 618 } 619 } 620 } finally { 621 c.close(); 622 } 623 } 624 625 /** 626 * Informs ExchangeService that an account has a new folder list; as a result, any existing 627 * folder might have become invalid. Therefore, we act as if the account has been deleted, and 628 * then we reinitialize it. 629 * 630 * @param acctId 631 */ 632 static public void stopNonAccountMailboxSyncsForAccount(long acctId) { 633 SyncManager exchangeService = INSTANCE; 634 if (exchangeService != null) { 635 exchangeService.stopAccountSyncs(acctId, false); 636 kick("reload folder list"); 637 } 638 } 639 640 /** 641 * Start up the ExchangeService service if it's not already running 642 * This is a stopgap for cases in which ExchangeService died (due to a crash somewhere in 643 * com.android.email) and hasn't been restarted. See the comment for onCreate for details 644 */ 645 static void checkExchangeServiceServiceRunning() { 646 SyncManager exchangeService = INSTANCE; 647 if (exchangeService == null) return; 648 if (sServiceThread == null) { 649 log("!!! checkExchangeServiceServiceRunning; starting service..."); 650 exchangeService.startService(new Intent(exchangeService, ExchangeService.class)); 651 } 652 } 653 654 @Override 655 public AccountObserver getAccountObserver( 656 Handler handler) { 657 return new AccountObserver(handler) { 658 @Override 659 public void newAccount(long acctId) { 660 Account acct = Account.restoreAccountWithId(getContext(), acctId); 661 if (acct == null) { 662 // This account is in a bad state; don't create the mailbox. 663 LogUtils.e(TAG, "Cannot initialize bad acctId: " + acctId); 664 return; 665 } 666 Mailbox main = new Mailbox(); 667 main.mDisplayName = Eas.ACCOUNT_MAILBOX_PREFIX; 668 main.mServerId = Eas.ACCOUNT_MAILBOX_PREFIX + System.nanoTime(); 669 main.mAccountKey = acct.mId; 670 main.mType = Mailbox.TYPE_EAS_ACCOUNT_MAILBOX; 671 main.mSyncInterval = Mailbox.CHECK_INTERVAL_PUSH; 672 main.mFlagVisible = false; 673 main.save(getContext()); 674 log("Initializing account: " + acct.mDisplayName); 675 } 676 }; 677 } 678 679 @Override 680 public void onStartup() { 681 // Do any required work to clean up our Mailboxes (this serves to upgrade 682 // mailboxes that existed prior to EmailProvider database version 17) 683 MailboxUtilities.fixupUninitializedParentKeys(this, getAccountsSelector()); 684 } 685 686 @Override 687 public AbstractSyncService getServiceForMailbox(Context context, 688 Mailbox m) { 689 switch(m.mType) { 690 case Mailbox.TYPE_EAS_ACCOUNT_MAILBOX: 691 return new EasAccountService(context, m); 692 case Mailbox.TYPE_OUTBOX: 693 return new EasOutboxService(context, m); 694 default: 695 return new EasSyncService(context, m); 696 } 697 } 698 699 @Override 700 public String getAccountsSelector() { 701 if (mEasAccountSelector == null) { 702 StringBuilder sb = new StringBuilder(ACCOUNT_KEY_IN); 703 boolean first = true; 704 synchronized (mAccountList) { 705 for (Account account : mAccountList) { 706 if (!first) { 707 sb.append(','); 708 } else { 709 first = false; 710 } 711 sb.append(account.mId); 712 } 713 } 714 sb.append(')'); 715 mEasAccountSelector = sb.toString(); 716 } 717 return mEasAccountSelector; 718 } 719 720 @Override 721 public String getAccountManagerType() { 722 return Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE; 723 } 724 725 @Override 726 public Intent getServiceIntent() { 727 return mIntent; 728 } 729 730 @Override 731 public Stub getCallbackProxy() { 732 return null; 733 } 734 735 /** 736 * Stop any ping in progress if required 737 * 738 * @param mailbox whose service has started 739 */ 740 @Override 741 public void onStartService(Mailbox mailbox) { 742 // If this is a ping mailbox, stop the ping 743 if (mailbox.mSyncInterval != Mailbox.CHECK_INTERVAL_PING) return; 744 long accountMailboxId = Mailbox.findMailboxOfType(this, mailbox.mAccountKey, 745 Mailbox.TYPE_EAS_ACCOUNT_MAILBOX); 746 // If our ping is running, stop it 747 final AbstractSyncService svc = getRunningService(accountMailboxId); 748 if (svc != null) { 749 log("Stopping ping due to sync of mailbox: " + mailbox.mDisplayName); 750 // Don't block; reset might perform network activity 751 new Thread(new Runnable() { 752 @Override 753 public void run() { 754 svc.reset(); 755 }}).start(); 756 } 757 } 758 } 759