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