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 
loadAccount(final Context context, final long accountId)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
doInBackground(Void... params)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
onPostExecute(Void result)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 
EasService()329     public EasService() {
330         super();
331         mSynchronizer = new PingSyncSynchronizer(this);
332     }
333 
334     @Override
onCreate()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
onDestroy()357     public void onDestroy() {
358         LogUtils.i(TAG, "onDestroy");
359         mSynchronizer.stopAllPings();
360     }
361 
362     @Override
onBind(final Intent intent)363     public IBinder onBind(final Intent intent) {
364         return mBinder;
365     }
366 
367     @Override
onStartCommand(final Intent intent, final int flags, final int startId)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 
doOperation(final EasOperation operation, final String loggingName)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      */
getAuthoritiesToSync(final android.accounts.Account account, final String[] authorities)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
setProtocolLogging(final boolean val)564     public static void setProtocolLogging(final boolean val) {
565         sProtocolLogging = val;
566     }
567 
568     @VisibleForTesting
setFileLogging(final boolean val)569     public static void setFileLogging(final boolean val) {
570         sFileLogging = val;
571     }
572 
getProtocolLogging()573     public static boolean getProtocolLogging() {
574         return sProtocolLogging;
575     }
576 
getFileLogging()577     public static boolean getFileLogging() {
578         return sFileLogging;
579     }
580 
581 }
582