Home | History | Annotate | Download | only in service
      1 /*
      2  * Copyright (C) 2010 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.email.service;
     18 
     19 import android.accounts.AccountManager;
     20 import android.accounts.AccountManagerCallback;
     21 import android.accounts.AccountManagerFuture;
     22 import android.accounts.AuthenticatorException;
     23 import android.accounts.OperationCanceledException;
     24 import android.app.Service;
     25 import android.content.ComponentName;
     26 import android.content.ContentProviderClient;
     27 import android.content.ContentResolver;
     28 import android.content.ContentUris;
     29 import android.content.ContentValues;
     30 import android.content.Context;
     31 import android.content.Intent;
     32 import android.content.pm.ActivityInfo;
     33 import android.content.pm.PackageManager;
     34 import android.content.res.Configuration;
     35 import android.content.res.Resources;
     36 import android.content.res.TypedArray;
     37 import android.content.res.XmlResourceParser;
     38 import android.database.Cursor;
     39 import android.net.Uri;
     40 import android.os.Bundle;
     41 import android.os.IBinder;
     42 import android.os.RemoteException;
     43 import android.provider.CalendarContract;
     44 import android.provider.CalendarContract.Calendars;
     45 import android.provider.CalendarContract.SyncState;
     46 import android.provider.ContactsContract;
     47 import android.provider.ContactsContract.RawContacts;
     48 import android.provider.SyncStateContract;
     49 import android.support.annotation.Nullable;
     50 import android.text.TextUtils;
     51 
     52 import com.android.email.R;
     53 import com.android.emailcommon.VendorPolicyLoader;
     54 import com.android.emailcommon.provider.Account;
     55 import com.android.emailcommon.provider.EmailContent;
     56 import com.android.emailcommon.provider.EmailContent.AccountColumns;
     57 import com.android.emailcommon.provider.EmailContent.HostAuthColumns;
     58 import com.android.emailcommon.provider.HostAuth;
     59 import com.android.emailcommon.service.EmailServiceProxy;
     60 import com.android.emailcommon.service.EmailServiceStatus;
     61 import com.android.emailcommon.service.EmailServiceVersion;
     62 import com.android.emailcommon.service.HostAuthCompat;
     63 import com.android.emailcommon.service.IEmailService;
     64 import com.android.emailcommon.service.IEmailServiceCallback;
     65 import com.android.emailcommon.service.SearchParams;
     66 import com.android.emailcommon.service.ServiceProxy;
     67 import com.android.emailcommon.service.SyncWindow;
     68 import com.android.mail.utils.LogUtils;
     69 import com.google.common.collect.ImmutableMap;
     70 
     71 import org.xmlpull.v1.XmlPullParserException;
     72 
     73 import java.io.IOException;
     74 import java.util.Collection;
     75 import java.util.Map;
     76 
     77 /**
     78  * Utility functions for EmailService support.
     79  */
     80 public class EmailServiceUtils {
     81     /**
     82      * Ask a service to kill its process. This is used when an account is deleted so that
     83      * no background thread that happens to be running will continue, possibly hitting an
     84      * NPE or other error when trying to operate on an account that no longer exists.
     85      * TODO: This is kind of a hack, it's only needed because we fail so badly if an account
     86      * is deleted out from under us while a sync or other operation is in progress. It would
     87      * be a lot cleaner if our background services could handle this without crashing.
     88      */
     89     public static void killService(Context context, String protocol) {
     90         EmailServiceInfo info = getServiceInfo(context, protocol);
     91         if (info != null && info.intentAction != null) {
     92             final Intent serviceIntent = getServiceIntent(info);
     93             serviceIntent.putExtra(ServiceProxy.EXTRA_FORCE_SHUTDOWN, true);
     94             context.startService(serviceIntent);
     95         }
     96     }
     97 
     98     /**
     99      * Starts an EmailService by protocol
    100      */
    101     public static void startService(Context context, String protocol) {
    102         EmailServiceInfo info = getServiceInfo(context, protocol);
    103         if (info != null && info.intentAction != null) {
    104             final Intent serviceIntent = getServiceIntent(info);
    105             context.startService(serviceIntent);
    106         }
    107     }
    108 
    109     /**
    110      * Starts all remote services
    111      */
    112     public static void startRemoteServices(Context context) {
    113         for (EmailServiceInfo info: getServiceInfoList(context)) {
    114             if (info.intentAction != null) {
    115                 final Intent serviceIntent = getServiceIntent(info);
    116                 context.startService(serviceIntent);
    117             }
    118         }
    119     }
    120 
    121     /**
    122      * Returns whether or not remote services are present on device
    123      */
    124     public static boolean areRemoteServicesInstalled(Context context) {
    125         for (EmailServiceInfo info: getServiceInfoList(context)) {
    126             if (info.intentAction != null) {
    127                 return true;
    128             }
    129         }
    130         return false;
    131     }
    132 
    133     /**
    134      * Starts all remote services
    135      */
    136     public static void setRemoteServicesLogging(Context context, int debugBits) {
    137         for (EmailServiceInfo info: getServiceInfoList(context)) {
    138             if (info.intentAction != null) {
    139                 EmailServiceProxy service =
    140                         EmailServiceUtils.getService(context, info.protocol);
    141                 if (service != null) {
    142                     try {
    143                         service.setLogging(debugBits);
    144                     } catch (RemoteException e) {
    145                         // Move along, nothing to see
    146                     }
    147                 }
    148             }
    149         }
    150     }
    151 
    152     /**
    153      * Determine if the EmailService is available
    154      */
    155     public static boolean isServiceAvailable(Context context, String protocol) {
    156         EmailServiceInfo info = getServiceInfo(context, protocol);
    157         if (info == null) return false;
    158         if (info.klass != null) return true;
    159         final Intent serviceIntent = getServiceIntent(info);
    160         return new EmailServiceProxy(context, serviceIntent).test();
    161     }
    162 
    163     private static Intent getServiceIntent(EmailServiceInfo info) {
    164         final Intent serviceIntent = new Intent(info.intentAction);
    165         serviceIntent.setPackage(info.intentPackage);
    166         return serviceIntent;
    167     }
    168 
    169     /**
    170      * For a given account id, return a service proxy if applicable, or null.
    171      *
    172      * @param accountId the message of interest
    173      * @return service proxy, or null if n/a
    174      */
    175     public static EmailServiceProxy getServiceForAccount(Context context, long accountId) {
    176         return getService(context, Account.getProtocol(context, accountId));
    177     }
    178 
    179     /**
    180      * Holder of service information (currently just name and class/intent); if there is a class
    181      * member, this is a (local, i.e. same process) service; otherwise, this is a remote service
    182      */
    183     public static class EmailServiceInfo {
    184         public String protocol;
    185         public String name;
    186         public String accountType;
    187         Class<? extends Service> klass;
    188         String intentAction;
    189         String intentPackage;
    190         public int port;
    191         public int portSsl;
    192         public boolean defaultSsl;
    193         public boolean offerTls;
    194         public boolean offerCerts;
    195         public boolean offerOAuth;
    196         public boolean usesSmtp;
    197         public boolean offerLocalDeletes;
    198         public int defaultLocalDeletes;
    199         public boolean offerPrefix;
    200         public boolean usesAutodiscover;
    201         public boolean offerLookback;
    202         public int defaultLookback;
    203         public boolean syncChanges;
    204         public boolean syncContacts;
    205         public boolean syncCalendar;
    206         public boolean offerAttachmentPreload;
    207         public CharSequence[] syncIntervalStrings;
    208         public CharSequence[] syncIntervals;
    209         public int defaultSyncInterval;
    210         public String inferPrefix;
    211         public boolean offerLoadMore;
    212         public boolean offerMoveTo;
    213         public boolean requiresSetup;
    214         public boolean hide;
    215         public boolean isGmailStub;
    216 
    217         @Override
    218         public String toString() {
    219             StringBuilder sb = new StringBuilder("Protocol: ");
    220             sb.append(protocol);
    221             sb.append(", ");
    222             sb.append(klass != null ? "Local" : "Remote");
    223             sb.append(" , Account Type: ");
    224             sb.append(accountType);
    225             return sb.toString();
    226         }
    227     }
    228 
    229     public static EmailServiceProxy getService(Context context, String protocol) {
    230         EmailServiceInfo info = null;
    231         // Handle the degenerate case here (account might have been deleted)
    232         if (protocol != null) {
    233             info = getServiceInfo(context, protocol);
    234         }
    235         if (info == null) {
    236             LogUtils.w(LogUtils.TAG, "Returning NullService for %s", protocol);
    237             return new EmailServiceProxy(context, NullService.class);
    238         } else  {
    239             return getServiceFromInfo(context, info);
    240         }
    241     }
    242 
    243     public static EmailServiceProxy getServiceFromInfo(Context context, EmailServiceInfo info) {
    244         if (info.klass != null) {
    245             return new EmailServiceProxy(context, info.klass);
    246         } else {
    247             final Intent serviceIntent = getServiceIntent(info);
    248             return new EmailServiceProxy(context, serviceIntent);
    249         }
    250     }
    251 
    252     public static EmailServiceInfo getServiceInfoForAccount(Context context, long accountId) {
    253         String protocol = Account.getProtocol(context, accountId);
    254         return getServiceInfo(context, protocol);
    255     }
    256 
    257     public static EmailServiceInfo getServiceInfo(Context context, String protocol) {
    258         return getServiceMap(context).get(protocol);
    259     }
    260 
    261     public static Collection<EmailServiceInfo> getServiceInfoList(Context context) {
    262         return getServiceMap(context).values();
    263     }
    264 
    265     private static void finishAccountManagerBlocker(AccountManagerFuture<?> future) {
    266         try {
    267             // Note: All of the potential errors are simply logged
    268             // here, as there is nothing to actually do about them.
    269             future.getResult();
    270         } catch (OperationCanceledException e) {
    271             LogUtils.w(LogUtils.TAG, e, "finishAccountManagerBlocker");
    272         } catch (AuthenticatorException e) {
    273             LogUtils.w(LogUtils.TAG, e, "finishAccountManagerBlocker");
    274         } catch (IOException e) {
    275             LogUtils.w(LogUtils.TAG, e, "finishAccountManagerBlocker");
    276         }
    277     }
    278 
    279     /**
    280      * Add an account to the AccountManager.
    281      * @param context Our {@link Context}.
    282      * @param account The {@link Account} we're adding.
    283      * @param email Whether the user wants to sync email on this account.
    284      * @param calendar Whether the user wants to sync calendar on this account.
    285      * @param contacts Whether the user wants to sync contacts on this account.
    286      * @param callback A callback for when the AccountManager is done.
    287      * @return The result of {@link AccountManager#addAccount}.
    288      */
    289     public static AccountManagerFuture<Bundle> setupAccountManagerAccount(final Context context,
    290             final Account account, final boolean email, final boolean calendar,
    291             final boolean contacts, final AccountManagerCallback<Bundle> callback) {
    292         final HostAuth hostAuthRecv =
    293                 HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv);
    294         return setupAccountManagerAccount(context, account, email, calendar, contacts,
    295                 hostAuthRecv, callback);
    296     }
    297 
    298     /**
    299      * Add an account to the AccountManager.
    300      * @param context Our {@link Context}.
    301      * @param account The {@link Account} we're adding.
    302      * @param email Whether the user wants to sync email on this account.
    303      * @param calendar Whether the user wants to sync calendar on this account.
    304      * @param contacts Whether the user wants to sync contacts on this account.
    305      * @param hostAuth HostAuth that identifies the protocol and password for this account.
    306      * @param callback A callback for when the AccountManager is done.
    307      * @return The result of {@link AccountManager#addAccount}.
    308      */
    309     public static AccountManagerFuture<Bundle> setupAccountManagerAccount(final Context context,
    310             final Account account, final boolean email, final boolean calendar,
    311             final boolean contacts, final HostAuth hostAuth,
    312             final AccountManagerCallback<Bundle> callback) {
    313         if (hostAuth == null) {
    314             return null;
    315         }
    316         // Set up username/password
    317         final Bundle options = new Bundle(5);
    318         options.putString(EasAuthenticatorService.OPTIONS_USERNAME, account.mEmailAddress);
    319         options.putString(EasAuthenticatorService.OPTIONS_PASSWORD, hostAuth.mPassword);
    320         options.putBoolean(EasAuthenticatorService.OPTIONS_CONTACTS_SYNC_ENABLED, contacts);
    321         options.putBoolean(EasAuthenticatorService.OPTIONS_CALENDAR_SYNC_ENABLED, calendar);
    322         options.putBoolean(EasAuthenticatorService.OPTIONS_EMAIL_SYNC_ENABLED, email);
    323         final EmailServiceInfo info = getServiceInfo(context, hostAuth.mProtocol);
    324         return AccountManager.get(context).addAccount(info.accountType, null, null, options, null,
    325                 callback, null);
    326     }
    327 
    328     public static void updateAccountManagerType(Context context,
    329             android.accounts.Account amAccount, final Map<String, String> protocolMap) {
    330         final ContentResolver resolver = context.getContentResolver();
    331         final Cursor c = resolver.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION,
    332                 AccountColumns.EMAIL_ADDRESS + "=?", new String[] { amAccount.name }, null);
    333         // That's odd, isn't it?
    334         if (c == null) return;
    335         try {
    336             if (c.moveToNext()) {
    337                 // Get the EmailProvider Account/HostAuth
    338                 final Account account = new Account();
    339                 account.restore(c);
    340                 final HostAuth hostAuth =
    341                         HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv);
    342                 if (hostAuth == null) {
    343                     return;
    344                 }
    345 
    346                 final String newProtocol = protocolMap.get(hostAuth.mProtocol);
    347                 if (newProtocol == null) {
    348                     // This account doesn't need updating.
    349                     return;
    350                 }
    351 
    352                 LogUtils.w(LogUtils.TAG, "Converting %s to %s", amAccount.name, newProtocol);
    353 
    354                 final ContentValues accountValues = new ContentValues();
    355                 int oldFlags = account.mFlags;
    356 
    357                 // Mark the provider account incomplete so it can't get reconciled away
    358                 account.mFlags |= Account.FLAGS_INCOMPLETE;
    359                 accountValues.put(AccountColumns.FLAGS, account.mFlags);
    360                 final Uri accountUri = ContentUris.withAppendedId(Account.CONTENT_URI, account.mId);
    361                 resolver.update(accountUri, accountValues, null, null);
    362 
    363                 // Change the HostAuth to reference the new protocol; this has to be done before
    364                 // trying to create the AccountManager account (below)
    365                 final ContentValues hostValues = new ContentValues();
    366                 hostValues.put(HostAuthColumns.PROTOCOL, newProtocol);
    367                 resolver.update(ContentUris.withAppendedId(HostAuth.CONTENT_URI, hostAuth.mId),
    368                         hostValues, null, null);
    369                 LogUtils.w(LogUtils.TAG, "Updated HostAuths");
    370 
    371                 try {
    372                     // Get current settings for the existing AccountManager account
    373                     boolean email = ContentResolver.getSyncAutomatically(amAccount,
    374                             EmailContent.AUTHORITY);
    375                     if (!email) {
    376                         // Try our old provider name
    377                         email = ContentResolver.getSyncAutomatically(amAccount,
    378                                 "com.android.email.provider");
    379                     }
    380                     final boolean contacts = ContentResolver.getSyncAutomatically(amAccount,
    381                             ContactsContract.AUTHORITY);
    382                     final boolean calendar = ContentResolver.getSyncAutomatically(amAccount,
    383                             CalendarContract.AUTHORITY);
    384                     LogUtils.w(LogUtils.TAG, "Email: %s, Contacts: %s Calendar: %s",
    385                             email, contacts, calendar);
    386 
    387                     // Get sync keys for calendar/contacts
    388                     final String amName = amAccount.name;
    389                     final String oldType = amAccount.type;
    390                     ContentProviderClient client = context.getContentResolver()
    391                             .acquireContentProviderClient(CalendarContract.CONTENT_URI);
    392                     byte[] calendarSyncKey = null;
    393                     try {
    394                         calendarSyncKey = SyncStateContract.Helpers.get(client,
    395                                 asCalendarSyncAdapter(SyncState.CONTENT_URI, amName, oldType),
    396                                 new android.accounts.Account(amName, oldType));
    397                     } catch (RemoteException e) {
    398                         LogUtils.w(LogUtils.TAG, "Get calendar key FAILED");
    399                     } finally {
    400                         client.release();
    401                     }
    402                     client = context.getContentResolver()
    403                             .acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
    404                     byte[] contactsSyncKey = null;
    405                     try {
    406                         contactsSyncKey = SyncStateContract.Helpers.get(client,
    407                                 ContactsContract.SyncState.CONTENT_URI,
    408                                 new android.accounts.Account(amName, oldType));
    409                     } catch (RemoteException e) {
    410                         LogUtils.w(LogUtils.TAG, "Get contacts key FAILED");
    411                     } finally {
    412                         client.release();
    413                     }
    414                     if (calendarSyncKey != null) {
    415                         LogUtils.w(LogUtils.TAG, "Got calendar key: %s",
    416                                 new String(calendarSyncKey));
    417                     }
    418                     if (contactsSyncKey != null) {
    419                         LogUtils.w(LogUtils.TAG, "Got contacts key: %s",
    420                                 new String(contactsSyncKey));
    421                     }
    422 
    423                     // Set up a new AccountManager account with new type and old settings
    424                     AccountManagerFuture<?> amFuture = setupAccountManagerAccount(context, account,
    425                             email, calendar, contacts, null);
    426                     finishAccountManagerBlocker(amFuture);
    427                     LogUtils.w(LogUtils.TAG, "Created new AccountManager account");
    428 
    429                     // TODO: Clean up how we determine the type.
    430                     final String accountType = protocolMap.get(hostAuth.mProtocol + "_type");
    431                     // Move calendar and contacts data from the old account to the new one.
    432                     // We must do this before deleting the old account or the data is lost.
    433                     moveCalendarData(context.getContentResolver(), amName, oldType, accountType);
    434                     moveContactsData(context.getContentResolver(), amName, oldType, accountType);
    435 
    436                     // Delete the AccountManager account
    437                     amFuture = AccountManager.get(context)
    438                             .removeAccount(amAccount, null, null);
    439                     finishAccountManagerBlocker(amFuture);
    440                     LogUtils.w(LogUtils.TAG, "Deleted old AccountManager account");
    441 
    442                     // Restore sync keys for contacts/calendar
    443 
    444                     if (accountType != null &&
    445                             calendarSyncKey != null && calendarSyncKey.length != 0) {
    446                         client = context.getContentResolver()
    447                                 .acquireContentProviderClient(CalendarContract.CONTENT_URI);
    448                         try {
    449                             SyncStateContract.Helpers.set(client,
    450                                     asCalendarSyncAdapter(SyncState.CONTENT_URI, amName,
    451                                             accountType),
    452                                     new android.accounts.Account(amName, accountType),
    453                                     calendarSyncKey);
    454                             LogUtils.w(LogUtils.TAG, "Set calendar key...");
    455                         } catch (RemoteException e) {
    456                             LogUtils.w(LogUtils.TAG, "Set calendar key FAILED");
    457                         } finally {
    458                             client.release();
    459                         }
    460                     }
    461                     if (accountType != null &&
    462                             contactsSyncKey != null && contactsSyncKey.length != 0) {
    463                         client = context.getContentResolver()
    464                                 .acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
    465                         try {
    466                             SyncStateContract.Helpers.set(client,
    467                                     ContactsContract.SyncState.CONTENT_URI,
    468                                     new android.accounts.Account(amName, accountType),
    469                                     contactsSyncKey);
    470                             LogUtils.w(LogUtils.TAG, "Set contacts key...");
    471                         } catch (RemoteException e) {
    472                             LogUtils.w(LogUtils.TAG, "Set contacts key FAILED");
    473                         }
    474                     }
    475 
    476                     // That's all folks!
    477                     LogUtils.w(LogUtils.TAG, "Account update completed.");
    478                 } finally {
    479                     // Clear the incomplete flag on the provider account
    480                     accountValues.put(AccountColumns.FLAGS, oldFlags);
    481                     resolver.update(accountUri, accountValues, null, null);
    482                     LogUtils.w(LogUtils.TAG, "[Incomplete flag cleared]");
    483                 }
    484             }
    485         } finally {
    486             c.close();
    487         }
    488     }
    489 
    490     private static void moveCalendarData(final ContentResolver resolver, final String name,
    491             final String oldType, final String newType) {
    492         final Uri oldCalendars = Calendars.CONTENT_URI.buildUpon()
    493                 .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
    494                 .appendQueryParameter(Calendars.ACCOUNT_NAME, name)
    495                 .appendQueryParameter(Calendars.ACCOUNT_TYPE, oldType)
    496                 .build();
    497 
    498         // Update this calendar to have the new account type.
    499         final ContentValues values = new ContentValues();
    500         values.put(CalendarContract.Calendars.ACCOUNT_TYPE, newType);
    501         resolver.update(oldCalendars, values,
    502                 Calendars.ACCOUNT_NAME + "=? AND " + Calendars.ACCOUNT_TYPE + "=?",
    503                 new String[] {name, oldType});
    504     }
    505 
    506     private static void moveContactsData(final ContentResolver resolver, final String name,
    507             final String oldType, final String newType) {
    508         final Uri oldContacts = RawContacts.CONTENT_URI.buildUpon()
    509                 .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
    510                 .appendQueryParameter(RawContacts.ACCOUNT_NAME, name)
    511                 .appendQueryParameter(RawContacts.ACCOUNT_TYPE, oldType)
    512                 .build();
    513 
    514         // Update this calendar to have the new account type.
    515         final ContentValues values = new ContentValues();
    516         values.put(CalendarContract.Calendars.ACCOUNT_TYPE, newType);
    517         resolver.update(oldContacts, values, null, null);
    518     }
    519 
    520     private static final Configuration sOldConfiguration = new Configuration();
    521     private static Map<String, EmailServiceInfo> sServiceMap = null;
    522     private static final Object sServiceMapLock = new Object();
    523 
    524     /**
    525      * Parse services.xml file to find our available email services
    526      */
    527     private static Map<String, EmailServiceInfo> getServiceMap(final Context context) {
    528         synchronized (sServiceMapLock) {
    529             /**
    530              * We cache localized strings here, so make sure to regenerate the service map if
    531              * the locale changes
    532              */
    533             if (sServiceMap == null) {
    534                 sOldConfiguration.setTo(context.getResources().getConfiguration());
    535             }
    536 
    537             final int delta =
    538                     sOldConfiguration.updateFrom(context.getResources().getConfiguration());
    539 
    540             if (sServiceMap != null
    541                     && !Configuration.needNewResources(delta, ActivityInfo.CONFIG_LOCALE)) {
    542                 return sServiceMap;
    543             }
    544 
    545             final ImmutableMap.Builder<String, EmailServiceInfo> builder = ImmutableMap.builder();
    546             if (!context.getResources().getBoolean(R.bool.enable_services)) {
    547                 // Return an empty map if services have been disabled because this is the Email
    548                 // Tombstone app.
    549                 sServiceMap = builder.build();
    550                 return sServiceMap;
    551             }
    552 
    553             try {
    554                 final Resources res = context.getResources();
    555                 final XmlResourceParser xml = res.getXml(R.xml.services);
    556                 int xmlEventType;
    557                 // walk through senders.xml file.
    558                 while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) {
    559                     if (xmlEventType == XmlResourceParser.START_TAG &&
    560                             "emailservice".equals(xml.getName())) {
    561                         final EmailServiceInfo info = new EmailServiceInfo();
    562                         final TypedArray ta =
    563                                 res.obtainAttributes(xml, R.styleable.EmailServiceInfo);
    564                         info.protocol = ta.getString(R.styleable.EmailServiceInfo_protocol);
    565                         info.accountType = ta.getString(R.styleable.EmailServiceInfo_accountType);
    566                         info.name = ta.getString(R.styleable.EmailServiceInfo_name);
    567                         info.hide = ta.getBoolean(R.styleable.EmailServiceInfo_hide, false);
    568                         final String klass =
    569                                 ta.getString(R.styleable.EmailServiceInfo_serviceClass);
    570                         info.intentAction = ta.getString(R.styleable.EmailServiceInfo_intent);
    571                         info.intentPackage =
    572                                 ta.getString(R.styleable.EmailServiceInfo_intentPackage);
    573                         info.defaultSsl =
    574                                 ta.getBoolean(R.styleable.EmailServiceInfo_defaultSsl, false);
    575                         info.port = ta.getInteger(R.styleable.EmailServiceInfo_port, 0);
    576                         info.portSsl = ta.getInteger(R.styleable.EmailServiceInfo_portSsl, 0);
    577                         info.offerTls = ta.getBoolean(R.styleable.EmailServiceInfo_offerTls, false);
    578                         info.offerCerts =
    579                                 ta.getBoolean(R.styleable.EmailServiceInfo_offerCerts, false);
    580                         info.offerOAuth =
    581                                 ta.getBoolean(R.styleable.EmailServiceInfo_offerOAuth, false);
    582                         info.offerLocalDeletes =
    583                             ta.getBoolean(R.styleable.EmailServiceInfo_offerLocalDeletes, false);
    584                         info.defaultLocalDeletes =
    585                             ta.getInteger(R.styleable.EmailServiceInfo_defaultLocalDeletes,
    586                                     Account.DELETE_POLICY_ON_DELETE);
    587                         info.offerPrefix =
    588                             ta.getBoolean(R.styleable.EmailServiceInfo_offerPrefix, false);
    589                         info.usesSmtp = ta.getBoolean(R.styleable.EmailServiceInfo_usesSmtp, false);
    590                         info.usesAutodiscover =
    591                             ta.getBoolean(R.styleable.EmailServiceInfo_usesAutodiscover, false);
    592                         info.offerLookback =
    593                             ta.getBoolean(R.styleable.EmailServiceInfo_offerLookback, false);
    594                         info.defaultLookback =
    595                             ta.getInteger(R.styleable.EmailServiceInfo_defaultLookback,
    596                                     SyncWindow.SYNC_WINDOW_3_DAYS);
    597                         info.syncChanges =
    598                             ta.getBoolean(R.styleable.EmailServiceInfo_syncChanges, false);
    599                         info.syncContacts =
    600                             ta.getBoolean(R.styleable.EmailServiceInfo_syncContacts, false);
    601                         info.syncCalendar =
    602                             ta.getBoolean(R.styleable.EmailServiceInfo_syncCalendar, false);
    603                         info.offerAttachmentPreload =
    604                             ta.getBoolean(R.styleable.EmailServiceInfo_offerAttachmentPreload,
    605                                     false);
    606                         info.syncIntervalStrings =
    607                             ta.getTextArray(R.styleable.EmailServiceInfo_syncIntervalStrings);
    608                         info.syncIntervals =
    609                             ta.getTextArray(R.styleable.EmailServiceInfo_syncIntervals);
    610                         info.defaultSyncInterval =
    611                             ta.getInteger(R.styleable.EmailServiceInfo_defaultSyncInterval, 15);
    612                         info.inferPrefix = ta.getString(R.styleable.EmailServiceInfo_inferPrefix);
    613                         info.offerLoadMore =
    614                                 ta.getBoolean(R.styleable.EmailServiceInfo_offerLoadMore, false);
    615                         info.offerMoveTo =
    616                                 ta.getBoolean(R.styleable.EmailServiceInfo_offerMoveTo, false);
    617                         info.requiresSetup =
    618                                 ta.getBoolean(R.styleable.EmailServiceInfo_requiresSetup, false);
    619                         info.isGmailStub =
    620                                 ta.getBoolean(R.styleable.EmailServiceInfo_isGmailStub, false);
    621 
    622                         // Must have either "class" (local) or "intent" (remote)
    623                         if (klass != null) {
    624                             try {
    625                                 // noinspection unchecked
    626                                 info.klass = (Class<? extends Service>) Class.forName(klass);
    627                             } catch (ClassNotFoundException e) {
    628                                 throw new IllegalStateException(
    629                                         "Class not found in service descriptor: " + klass);
    630                             }
    631                         }
    632                         if (info.klass == null &&
    633                                 info.intentAction == null &&
    634                                 !info.isGmailStub) {
    635                             throw new IllegalStateException(
    636                                     "No class or intent action specified in service descriptor");
    637                         }
    638                         if (info.klass != null && info.intentAction != null) {
    639                             throw new IllegalStateException(
    640                                     "Both class and intent action specified in service descriptor");
    641                         }
    642                         builder.put(info.protocol, info);
    643                     }
    644                 }
    645             } catch (XmlPullParserException e) {
    646                 // ignore
    647             } catch (IOException e) {
    648                 // ignore
    649             }
    650             sServiceMap = builder.build();
    651             return sServiceMap;
    652         }
    653     }
    654 
    655     /**
    656      * Resolves a service name into a protocol name, or null if ambiguous
    657      * @param context for loading service map
    658      * @param accountType sync adapter service name
    659      * @return protocol name or null
    660      */
    661     public static @Nullable String getProtocolFromAccountType(final Context context,
    662             final String accountType) {
    663         if (TextUtils.isEmpty(accountType)) {
    664             return null;
    665         }
    666         final Map <String, EmailServiceInfo> serviceInfoMap = getServiceMap(context);
    667         String protocol = null;
    668         for (final EmailServiceInfo info : serviceInfoMap.values()) {
    669             if (TextUtils.equals(accountType, info.accountType)) {
    670                 if (!TextUtils.isEmpty(protocol) && !TextUtils.equals(protocol, info.protocol)) {
    671                     // More than one protocol matches
    672                     return null;
    673                 }
    674                 protocol = info.protocol;
    675             }
    676         }
    677         return protocol;
    678     }
    679 
    680     private static Uri asCalendarSyncAdapter(Uri uri, String account, String accountType) {
    681         return uri.buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
    682                 .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
    683                 .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
    684     }
    685 
    686     /**
    687      * A no-op service that can be returned for non-existent/null protocols
    688      */
    689     class NullService implements IEmailService {
    690         @Override
    691         public IBinder asBinder() {
    692             return null;
    693         }
    694 
    695         @Override
    696         public Bundle validate(HostAuthCompat hostauth) throws RemoteException {
    697             return null;
    698         }
    699 
    700         @Override
    701         public void loadAttachment(final IEmailServiceCallback cb, final long accountId,
    702                 final long attachmentId, final boolean background) throws RemoteException {
    703         }
    704 
    705         @Override
    706         public void updateFolderList(long accountId) throws RemoteException {}
    707 
    708         @Override
    709         public void setLogging(int flags) throws RemoteException {
    710         }
    711 
    712         @Override
    713         public Bundle autoDiscover(String userName, String password) throws RemoteException {
    714             return null;
    715         }
    716 
    717         @Override
    718         public void sendMeetingResponse(long messageId, int response) throws RemoteException {
    719         }
    720 
    721         @Override
    722         public void deleteExternalAccountPIMData(final String emailAddress) throws RemoteException {
    723         }
    724 
    725         @Override
    726         public int searchMessages(long accountId, SearchParams params, long destMailboxId)
    727                 throws RemoteException {
    728             return 0;
    729         }
    730 
    731         @Override
    732         public void sendMail(long accountId) throws RemoteException {
    733         }
    734 
    735         @Override
    736         public void pushModify(long accountId) throws RemoteException {
    737         }
    738 
    739         @Override
    740         public int sync(final long accountId, final Bundle syncExtras) {
    741             return EmailServiceStatus.SUCCESS;
    742         }
    743 
    744         public int getApiVersion() {
    745             return EmailServiceVersion.CURRENT;
    746         }
    747     }
    748 
    749     public static void setComponentStatus(final Context context, Class<?> clazz, boolean enabled) {
    750         final ComponentName c = new ComponentName(context, clazz.getName());
    751         context.getPackageManager().setComponentEnabledSetting(c,
    752                 enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
    753                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
    754                 PackageManager.DONT_KILL_APP);
    755     }
    756 
    757     /**
    758      * This is a helper function that enables the proper Exchange component and disables
    759      * the other Exchange component ensuring that only one is enabled at a time.
    760      */
    761     public static void enableExchangeComponent(final Context context) {
    762         if (VendorPolicyLoader.getInstance(context).useAlternateExchangeStrings()) {
    763             LogUtils.d(LogUtils.TAG, "Enabling alternate EAS authenticator");
    764             setComponentStatus(context, EasAuthenticatorServiceAlternate.class, true);
    765             setComponentStatus(context, EasAuthenticatorService.class, false);
    766         } else {
    767             LogUtils.d(LogUtils.TAG, "Enabling EAS authenticator");
    768             setComponentStatus(context, EasAuthenticatorService.class, true);
    769             setComponentStatus(context,
    770                     EasAuthenticatorServiceAlternate.class, false);
    771         }
    772     }
    773 
    774     public static void disableExchangeComponents(final Context context) {
    775         LogUtils.d(LogUtils.TAG, "Disabling EAS authenticators");
    776         setComponentStatus(context, EasAuthenticatorServiceAlternate.class, false);
    777         setComponentStatus(context, EasAuthenticatorService.class, false);
    778     }
    779 
    780 }
    781