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 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