Home | History | Annotate | Download | only in service
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.exchange.service;
     18 
     19 import android.app.Service;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.SharedPreferences;
     24 import android.database.Cursor;
     25 import android.os.AsyncTask;
     26 import android.os.Bundle;
     27 import android.os.IBinder;
     28 import android.provider.CalendarContract;
     29 import android.provider.ContactsContract;
     30 import android.text.TextUtils;
     31 
     32 import com.android.emailcommon.TempDirectory;
     33 import com.android.emailcommon.provider.Account;
     34 import com.android.emailcommon.provider.EmailContent;
     35 import com.android.emailcommon.provider.HostAuth;
     36 import com.android.emailcommon.provider.Mailbox;
     37 import com.android.emailcommon.service.EmailServiceProxy;
     38 import com.android.emailcommon.service.EmailServiceStatus;
     39 import com.android.emailcommon.service.EmailServiceVersion;
     40 import com.android.emailcommon.service.HostAuthCompat;
     41 import com.android.emailcommon.service.IEmailService;
     42 import com.android.emailcommon.service.IEmailServiceCallback;
     43 import com.android.emailcommon.service.SearchParams;
     44 import com.android.emailcommon.service.ServiceProxy;
     45 import com.android.exchange.Eas;
     46 import com.android.exchange.eas.EasAutoDiscover;
     47 import com.android.exchange.eas.EasFolderSync;
     48 import com.android.exchange.eas.EasFullSyncOperation;
     49 import com.android.exchange.eas.EasLoadAttachment;
     50 import com.android.exchange.eas.EasOperation;
     51 import com.android.exchange.eas.EasPing;
     52 import com.android.exchange.eas.EasSearch;
     53 import com.android.exchange.eas.EasSearchGal;
     54 import com.android.exchange.eas.EasSendMeetingResponse;
     55 import com.android.exchange.eas.EasSyncCalendar;
     56 import com.android.exchange.eas.EasSyncContacts;
     57 import com.android.exchange.provider.GalResult;
     58 import com.android.mail.utils.LogUtils;
     59 import com.google.common.annotations.VisibleForTesting;
     60 
     61 import java.util.HashSet;
     62 import java.util.Set;
     63 
     64 /**
     65  * Service to handle all communication with the EAS server. Note that this is completely decoupled
     66  * from the sync adapters; sync adapters should make blocking calls on this service to actually
     67  * perform any operations.
     68  */
     69 public class EasService extends Service {
     70 
     71     private static final String TAG = Eas.LOG_TAG;
     72 
     73     private static final String PREFERENCES_FILE = "ExchangePrefs";
     74     private static final String PROTOCOL_LOGGING_PREF = "ProtocolLogging";
     75     private static final String FILE_LOGGING_PREF = "FileLogging";
     76 
     77     public static final String EXTRA_START_PING = "START_PING";
     78     public static final String EXTRA_PING_ACCOUNT = "PING_ACCOUNT";
     79 
     80     /**
     81      * The content authorities that can be synced for EAS accounts. Initialization must wait until
     82      * after we have a chance to call {@link EmailContent#init} (and, for future content types,
     83      * possibly other initializations) because that's how we can know what the email authority is.
     84      */
     85     private static String[] AUTHORITIES_TO_SYNC;
     86 
     87     /** Bookkeeping for ping tasks & sync threads management. */
     88     private final PingSyncSynchronizer mSynchronizer;
     89 
     90     private static boolean sProtocolLogging;
     91     private static boolean sFileLogging;
     92 
     93     /**
     94      * Implementation of the IEmailService interface.
     95      * For the most part these calls should consist of creating the correct {@link EasOperation}
     96      * class and calling {@link #doOperation} with it.
     97      */
     98     private final IEmailService.Stub mBinder = new IEmailService.Stub() {
     99         @Override
    100         public void loadAttachment(final IEmailServiceCallback callback, final long accountId,
    101                 final long attachmentId, final boolean background) {
    102             LogUtils.d(TAG, "IEmailService.loadAttachment: %d", attachmentId);
    103             final Account account = loadAccount(EasService.this, accountId);
    104             if (account != null) {
    105                 final EasLoadAttachment operation = new EasLoadAttachment(EasService.this, account,
    106                         attachmentId, callback);
    107                 doOperation(operation, "IEmailService.loadAttachment");
    108             }
    109         }
    110 
    111         @Override
    112         public void updateFolderList(final long accountId) {
    113             LogUtils.d(TAG, "IEmailService.updateFolderList: %d", accountId);
    114             final Account account = loadAccount(EasService.this, accountId);
    115             if (account != null) {
    116                 final EasFolderSync operation = new EasFolderSync(EasService.this, account);
    117                 doOperation(operation, "IEmailService.updateFolderList");
    118             }
    119         }
    120 
    121         public void sendMail(final long accountId) {
    122             // TODO: We should get rid of sendMail, and this is done in sync.
    123             LogUtils.wtf(TAG, "unexpected call to EasService.sendMail");
    124         }
    125 
    126         public int sync(final long accountId, Bundle syncExtras) {
    127             LogUtils.d(TAG, "IEmailService.updateFolderList: %d", accountId);
    128             final Account account = loadAccount(EasService.this, accountId);
    129             if (account != null) {
    130                 EasFullSyncOperation op = new EasFullSyncOperation(EasService.this, account,
    131                         syncExtras);
    132                 final int result = doOperation(op, "IEmailService.sync");
    133                 if (result == EasFullSyncOperation.RESULT_SECURITY_HOLD) {
    134                     LogUtils.i(LogUtils.TAG, "Security Hold trying to sync");
    135                     return EmailServiceStatus.INTERNAL_ERROR;
    136                 }
    137                 return convertToEmailServiceStatus(result);
    138             } else {
    139                 return EmailServiceStatus.INTERNAL_ERROR;
    140             }
    141         }
    142 
    143         @Override
    144         public void pushModify(final long accountId) {
    145             LogUtils.d(TAG, "IEmailService.pushModify: %d", accountId);
    146             final Account account = Account.restoreAccountWithId(EasService.this, accountId);
    147             if (pingNeededForAccount(EasService.this, account)) {
    148                 mSynchronizer.pushModify(account);
    149             } else {
    150                 mSynchronizer.pushStop(accountId);
    151             }
    152         }
    153 
    154         @Override
    155         public Bundle validate(final HostAuthCompat hostAuthCom) {
    156             LogUtils.d(TAG, "IEmailService.validate");
    157             final HostAuth hostAuth = hostAuthCom.toHostAuth();
    158             final EasFolderSync operation = new EasFolderSync(EasService.this, hostAuth);
    159             doOperation(operation, "IEmailService.validate");
    160             return operation.getValidationResult();
    161         }
    162 
    163         @Override
    164         public int searchMessages(final long accountId, final SearchParams searchParams,
    165                 final long destMailboxId) {
    166             LogUtils.d(TAG, "IEmailService.searchMessages");
    167             final Account account = loadAccount(EasService.this, accountId);
    168             if (account != null) {
    169                 final EasSearch operation = new EasSearch(EasService.this, account, searchParams,
    170                         destMailboxId);
    171                 doOperation(operation, "IEmailService.searchMessages");
    172                 return operation.getTotalResults();
    173             } else {
    174                 return 0;
    175             }
    176         }
    177 
    178         @Override
    179         public void sendMeetingResponse(final long messageId, final int response) {
    180             EmailContent.Message msg = EmailContent.Message.restoreMessageWithId(EasService.this,
    181                     messageId);
    182             LogUtils.d(TAG, "IEmailService.sendMeetingResponse");
    183             if (msg == null) {
    184                 LogUtils.e(TAG, "Could not load message %d in sendMeetingResponse", messageId);
    185                 return;
    186             }
    187             final Account account = loadAccount(EasService.this, msg.mAccountKey);
    188             if (account != null) {
    189                 final EasSendMeetingResponse operation = new EasSendMeetingResponse(EasService.this,
    190                         account, msg, response);
    191                 doOperation(operation, "IEmailService.sendMeetingResponse");
    192             }
    193         }
    194 
    195         @Override
    196         public Bundle autoDiscover(final String username, final String password) {
    197             final String domain = EasAutoDiscover.getDomain(username);
    198             for (int attempt = 0; attempt <= EasAutoDiscover.ATTEMPT_MAX; attempt++) {
    199                 LogUtils.d(TAG, "autodiscover attempt %d", attempt);
    200                 final String uri = EasAutoDiscover.genUri(domain, attempt);
    201                 Bundle result = autoDiscoverInternal(uri, attempt, username, password, true);
    202                 int resultCode = result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE);
    203                 if (resultCode != EasAutoDiscover.RESULT_BAD_RESPONSE) {
    204                     return result;
    205                 } else {
    206                     LogUtils.d(TAG, "got BAD_RESPONSE");
    207                 }
    208             }
    209             return null;
    210         }
    211 
    212         private Bundle autoDiscoverInternal(final String uri, final int attempt,
    213                                             final String username, final String password,
    214                                             final boolean canRetry) {
    215             final EasAutoDiscover op = new EasAutoDiscover(EasService.this, uri, attempt,
    216                     username, password);
    217             final int result = op.performOperation();
    218             if (result == EasAutoDiscover.RESULT_REDIRECT) {
    219                 // Try again recursively with the new uri. TODO we should limit the number of redirects.
    220                 final String redirectUri = op.getRedirectUri();
    221                 return autoDiscoverInternal(redirectUri, attempt, username, password, canRetry);
    222             } else if (result == EasAutoDiscover.RESULT_SC_UNAUTHORIZED) {
    223                 if (canRetry && username.contains("@")) {
    224                     // Try again using the bare user name
    225                     final int atSignIndex = username.indexOf('@');
    226                     final String bareUsername = username.substring(0, atSignIndex);
    227                     LogUtils.d(TAG, "%d received; trying username: %s", result, atSignIndex);
    228                     // Try again recursively, but this time don't allow retries for username.
    229                     return autoDiscoverInternal(uri, attempt, bareUsername, password, false);
    230                 } else {
    231                     // Either we're already on our second try or the username didn't have an "@"
    232                     // to begin with. Either way, failure.
    233                     final Bundle bundle = new Bundle(1);
    234                     bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
    235                             EasAutoDiscover.RESULT_OTHER_FAILURE);
    236                     return bundle;
    237                 }
    238             } else if (result != EasAutoDiscover.RESULT_OK) {
    239                 // Return failure, we'll try again with an alternate address
    240                 final Bundle bundle = new Bundle(1);
    241                 bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
    242                         EasAutoDiscover.RESULT_BAD_RESPONSE);
    243                 return bundle;
    244             }
    245             // Success.
    246             return op.getResultBundle();
    247         }
    248 
    249         @Override
    250         public void setLogging(final int flags) {
    251             sProtocolLogging = ((flags & EmailServiceProxy.DEBUG_EXCHANGE_BIT) != 0);
    252             sFileLogging = ((flags & EmailServiceProxy.DEBUG_FILE_BIT) != 0);
    253             SharedPreferences sharedPrefs = EasService.this.getSharedPreferences(PREFERENCES_FILE,
    254                     Context.MODE_PRIVATE);
    255             sharedPrefs.edit().putBoolean(PROTOCOL_LOGGING_PREF, sProtocolLogging).apply();
    256             sharedPrefs.edit().putBoolean(FILE_LOGGING_PREF, sFileLogging).apply();
    257             LogUtils.d(TAG, "IEmailService.setLogging %d, storing to shared pref", flags);
    258         }
    259 
    260         @Override
    261         public void deleteExternalAccountPIMData(final String emailAddress) {
    262             LogUtils.d(TAG, "IEmailService.deleteAccountPIMData");
    263             if (emailAddress != null) {
    264                 // TODO: stop pings
    265                 final Context context = EasService.this;
    266                 EasSyncContacts.wipeAccountFromContentProvider(context, emailAddress);
    267                 EasSyncCalendar.wipeAccountFromContentProvider(context, emailAddress);
    268             }
    269         }
    270 
    271         public int getApiVersion() {
    272             return EmailServiceVersion.CURRENT;
    273         }
    274     };
    275 
    276     private static Account loadAccount(final Context context, final long accountId) {
    277         Account account = Account.restoreAccountWithId(context, accountId);
    278         if (account == null) {
    279             LogUtils.e(TAG, "Could not load account %d", accountId);
    280         }
    281         return account;
    282     }
    283 
    284     /**
    285      * Content selection string for getting all accounts that are configured for push.
    286      * TODO: Add protocol check so that we don't get e.g. IMAP accounts here.
    287      * (Not currently necessary but eventually will be.)
    288      */
    289     private static final String PUSH_ACCOUNTS_SELECTION =
    290             EmailContent.AccountColumns.SYNC_INTERVAL +
    291                     "=" + Integer.toString(Account.CHECK_INTERVAL_PUSH);
    292 
    293     /** {@link AsyncTask} to restart pings for all accounts that need it. */
    294     private class RestartPingsTask extends AsyncTask<Void, Void, Void> {
    295         private boolean mHasRestartedPing = false;
    296 
    297         @Override
    298         protected Void doInBackground(Void... params) {
    299             LogUtils.i(TAG, "RestartPingTask");
    300             final Cursor c = EasService.this.getContentResolver().query(Account.CONTENT_URI,
    301                     Account.CONTENT_PROJECTION, PUSH_ACCOUNTS_SELECTION, null, null);
    302             if (c != null) {
    303                 try {
    304                     while (c.moveToNext()) {
    305                         final Account account = new Account();
    306                         account.restore(c);
    307                         LogUtils.i(TAG, "RestartPingsTask starting ping for %d", account.getId());
    308                         if (pingNeededForAccount(EasService.this, account)) {
    309                             mHasRestartedPing = true;
    310                             EasService.this.mSynchronizer.pushModify(account);
    311                         }
    312                     }
    313                 } finally {
    314                     c.close();
    315                 }
    316             }
    317             return null;
    318         }
    319 
    320         @Override
    321         protected void onPostExecute(Void result) {
    322             if (!mHasRestartedPing) {
    323                 LogUtils.i(TAG, "RestartPingsTask did not start any pings.");
    324                 EasService.this.mSynchronizer.stopServiceIfIdle();
    325             }
    326         }
    327     }
    328 
    329     public EasService() {
    330         super();
    331         mSynchronizer = new PingSyncSynchronizer(this);
    332     }
    333 
    334     @Override
    335     public void onCreate() {
    336         LogUtils.i(TAG, "EasService.onCreate");
    337         super.onCreate();
    338         TempDirectory.setTempDirectory(this);
    339         EmailContent.init(this);
    340         AUTHORITIES_TO_SYNC = new String[] {
    341                 EmailContent.AUTHORITY,
    342                 CalendarContract.AUTHORITY,
    343                 ContactsContract.AUTHORITY
    344         };
    345         SharedPreferences sharedPrefs = EasService.this.getSharedPreferences(PREFERENCES_FILE,
    346                 Context.MODE_PRIVATE);
    347         sProtocolLogging = sharedPrefs.getBoolean(PROTOCOL_LOGGING_PREF, false);
    348         sFileLogging = sharedPrefs.getBoolean(FILE_LOGGING_PREF, false);
    349         // Restart push for all accounts that need it. Because this requires DB loads, we do it in
    350         // an AsyncTask, and we startService to ensure that we stick around long enough for the
    351         // task to complete. The task will stop the service if necessary after it's done.
    352         startService(new Intent(this, EasService.class));
    353         new RestartPingsTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    354     }
    355 
    356     @Override
    357     public void onDestroy() {
    358         LogUtils.i(TAG, "onDestroy");
    359         mSynchronizer.stopAllPings();
    360     }
    361 
    362     @Override
    363     public IBinder onBind(final Intent intent) {
    364         return mBinder;
    365     }
    366 
    367     @Override
    368     public int onStartCommand(final Intent intent, final int flags, final int startId) {
    369         if (intent != null &&
    370                 TextUtils.equals(Eas.EXCHANGE_SERVICE_INTENT_ACTION, intent.getAction())) {
    371             if (intent.getBooleanExtra(ServiceProxy.EXTRA_FORCE_SHUTDOWN, false)) {
    372                 // We've been asked to forcibly shutdown. This happens if email accounts are
    373                 // deleted, otherwise we can get errors if services are still running for
    374                 // accounts that are now gone.
    375                 // TODO: This is kind of a hack, it would be nicer if we could handle it correctly
    376                 // if accounts disappear out from under us.
    377                 LogUtils.d(TAG, "Forced shutdown, killing process");
    378                 System.exit(-1);
    379             } else if (intent.getBooleanExtra(EXTRA_START_PING, false)) {
    380                 LogUtils.d(LogUtils.TAG, "Restarting ping");
    381                 final Account account = intent.getParcelableExtra(EXTRA_PING_ACCOUNT);
    382                 final android.accounts.Account amAccount =
    383                                 new android.accounts.Account(account.mEmailAddress,
    384                                     Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE);
    385                 EasPing.requestPing(amAccount);
    386             }
    387         }
    388         return START_STICKY;
    389     }
    390 
    391     public int doOperation(final EasOperation operation, final String loggingName) {
    392         LogUtils.d(TAG, "%s: %d", loggingName, operation.getAccountId());
    393         mSynchronizer.syncStart(operation.getAccountId());
    394         int result = EasOperation.RESULT_MIN_OK_RESULT;
    395         // TODO: Do we need a wakelock here? For RPC coming from sync adapters, no -- the SA
    396         // already has one. But for others, maybe? Not sure what's guaranteed for AIDL calls.
    397         // If we add a wakelock (or anything else for that matter) here, must remember to undo
    398         // it in the finally block below.
    399         // On the other hand, even for SAs, it doesn't hurt to get a wakelock here.
    400         try {
    401             result = operation.performOperation();
    402             LogUtils.d(TAG, "Operation result %d", result);
    403             return result;
    404         } finally {
    405             mSynchronizer.syncEnd(result < EasOperation.RESULT_MIN_OK_RESULT,
    406                     operation.getAccount());
    407         }
    408     }
    409 
    410     /**
    411      * Determine whether this account is configured with folders that are ready for push
    412      * notifications.
    413      * @param account The {@link Account} that we're interested in.
    414      * @param context The context
    415      * @return Whether this account needs to ping.
    416      */
    417     public static boolean pingNeededForAccount(final Context context, final Account account) {
    418         // Check account existence.
    419         if (account == null || account.mId == Account.NO_ACCOUNT) {
    420             LogUtils.d(TAG, "Do not ping: Account not found or not valid");
    421             return false;
    422         }
    423 
    424         // Check if account is configured for a push sync interval.
    425         if (account.mSyncInterval != Account.CHECK_INTERVAL_PUSH) {
    426             LogUtils.d(TAG, "Do not ping: Account %d not configured for push", account.mId);
    427             return false;
    428         }
    429 
    430         // Check security hold status of the account.
    431         if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
    432             LogUtils.d(TAG, "Do not ping: Account %d is on security hold", account.mId);
    433             return false;
    434         }
    435 
    436         // Check if the account has performed at least one sync so far (accounts must perform
    437         // the initial sync before push is possible).
    438         if (EmailContent.isInitialSyncKey(account.mSyncKey)) {
    439             LogUtils.d(TAG, "Do not ping: Account %d has not done initial sync", account.mId);
    440             return false;
    441         }
    442 
    443         // Check that there's at least one mailbox that is both configured for push notifications,
    444         // and whose content type is enabled for sync in the account manager.
    445         final android.accounts.Account amAccount = new android.accounts.Account(
    446                         account.mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE);
    447 
    448         final Set<String> authsToSync = getAuthoritiesToSync(amAccount, AUTHORITIES_TO_SYNC);
    449         // If we have at least one sync-enabled content type, check for syncing mailboxes.
    450         if (!authsToSync.isEmpty()) {
    451             final Cursor c = Mailbox.getMailboxesForPush(context.getContentResolver(), account.mId);
    452             if (c != null) {
    453                 try {
    454                     while (c.moveToNext()) {
    455                         final int mailboxType = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
    456                         if (authsToSync.contains(Mailbox.getAuthority(mailboxType))) {
    457                             LogUtils.d(TAG, "should ping for account %d", account.mId);
    458                             return true;
    459                         }
    460                     }
    461                 } finally {
    462                     c.close();
    463                 }
    464             }
    465         }
    466         LogUtils.d(TAG, "Do not ping: Account %d has no folders configured for push", account.mId);
    467         return false;
    468     }
    469 
    470     static public GalResult searchGal(final Context context, final long accountId,
    471                                       final String filter, final int limit) {
    472         GalResult galResult = null;
    473         final Account account = loadAccount(context, accountId);
    474         if (account != null) {
    475             final EasSearchGal operation = new EasSearchGal(context, account, filter, limit);
    476             // We don't use doOperation() here for two reasons:
    477             // 1. This is a static function, doOperation is not, and we don't have an instance of
    478             // EasService.
    479             // 2. All doOperation() does besides this is stop the ping and then restart it. This is
    480             // required during syncs, but not for GalSearches.
    481             final int result = operation.performOperation();
    482             if (result == EasSearchGal.RESULT_OK) {
    483                 galResult = operation.getResult();
    484             }
    485         }
    486         return galResult;
    487     }
    488 
    489     /**
    490      * Converts from an EasOperation status to a status code defined in EmailServiceStatus.
    491      * This is used to communicate the status of a sync operation to the caller.
    492      * @param easStatus result returned from an EasOperation
    493      * @return EmailServiceStatus
    494      */
    495     private int convertToEmailServiceStatus(int easStatus) {
    496         if (easStatus >= EasOperation.RESULT_MIN_OK_RESULT) {
    497             return EmailServiceStatus.SUCCESS;
    498         }
    499         switch (easStatus) {
    500             case EasOperation.RESULT_ABORT:
    501             case EasOperation.RESULT_RESTART:
    502                 // This should only happen if a ping is interruped for some reason. We would not
    503                 // expect see that here, since this should only be called for a sync.
    504                 LogUtils.e(TAG, "Abort or Restart easStatus");
    505                 return EmailServiceStatus.SUCCESS;
    506 
    507             case EasOperation.RESULT_TOO_MANY_REDIRECTS:
    508                 return EmailServiceStatus.INTERNAL_ERROR;
    509 
    510             case EasOperation.RESULT_NETWORK_PROBLEM:
    511                 // This is due to an IO error, we need the caller to know about this so that it
    512                 // can let the syncManager know.
    513                 return EmailServiceStatus.IO_ERROR;
    514 
    515             case EasOperation.RESULT_FORBIDDEN:
    516             case EasOperation.RESULT_AUTHENTICATION_ERROR:
    517                 return EmailServiceStatus.LOGIN_FAILED;
    518 
    519             case EasOperation.RESULT_PROVISIONING_ERROR:
    520                 return EmailServiceStatus.PROVISIONING_ERROR;
    521 
    522             case EasOperation.RESULT_CLIENT_CERTIFICATE_REQUIRED:
    523                 return EmailServiceStatus.CLIENT_CERTIFICATE_ERROR;
    524 
    525             case EasOperation.RESULT_PROTOCOL_VERSION_UNSUPPORTED:
    526                 return EmailServiceStatus.PROTOCOL_ERROR;
    527 
    528             case EasOperation.RESULT_INITIALIZATION_FAILURE:
    529             case EasOperation.RESULT_HARD_DATA_FAILURE:
    530             case EasOperation.RESULT_OTHER_FAILURE:
    531                 return EmailServiceStatus.INTERNAL_ERROR;
    532 
    533             case EasOperation.RESULT_NON_FATAL_ERROR:
    534                 // We do not expect to see this error here: This should be consumed in
    535                 // EasFullSyncOperation. The only case this occurs in is when we try to send
    536                 // a message in the outbox, and there's some problem with the message locally
    537                 // that prevents it from being sent. We return a
    538                 LogUtils.e(TAG, "Other non-fatal error easStatus %d", easStatus);
    539                 return EmailServiceStatus.SUCCESS;
    540         }
    541         LogUtils.e(TAG, "Unexpected easStatus %d", easStatus);
    542         return EmailServiceStatus.INTERNAL_ERROR;
    543     }
    544 
    545 
    546     /**
    547      * Determine which content types are set to sync for an account.
    548      * @param account The account whose sync settings we're looking for.
    549      * @param authorities All possible authorities we could care about.
    550      * @return The authorities for the content types we want to sync for account.
    551      */
    552     public static Set<String> getAuthoritiesToSync(final android.accounts.Account account,
    553                                                     final String[] authorities) {
    554         final HashSet<String> authsToSync = new HashSet();
    555         for (final String authority : authorities) {
    556             if (ContentResolver.getSyncAutomatically(account, authority)) {
    557                 authsToSync.add(authority);
    558             }
    559         }
    560         return authsToSync;
    561     }
    562 
    563     @VisibleForTesting
    564     public static void setProtocolLogging(final boolean val) {
    565         sProtocolLogging = val;
    566     }
    567 
    568     @VisibleForTesting
    569     public static void setFileLogging(final boolean val) {
    570         sFileLogging = val;
    571     }
    572 
    573     public static boolean getProtocolLogging() {
    574         return sProtocolLogging;
    575     }
    576 
    577     public static boolean getFileLogging() {
    578         return sFileLogging;
    579     }
    580 
    581 }
    582