• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
killService(Context context, String protocol)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      */
startService(Context context, String protocol)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      */
startRemoteServices(Context context)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      */
areRemoteServicesInstalled(Context context)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      */
setRemoteServicesLogging(Context context, int debugBits)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      */
isServiceAvailable(Context context, String protocol)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 
getServiceIntent(EmailServiceInfo info)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      */
getServiceForAccount(Context context, long accountId)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
toString()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 
getService(Context context, String protocol)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 
getServiceFromInfo(Context context, EmailServiceInfo info)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 
getServiceInfoForAccount(Context context, long accountId)252     public static EmailServiceInfo getServiceInfoForAccount(Context context, long accountId) {
253         String protocol = Account.getProtocol(context, accountId);
254         return getServiceInfo(context, protocol);
255     }
256 
getServiceInfo(Context context, String protocol)257     public static EmailServiceInfo getServiceInfo(Context context, String protocol) {
258         return getServiceMap(context).get(protocol);
259     }
260 
getServiceInfoList(Context context)261     public static Collection<EmailServiceInfo> getServiceInfoList(Context context) {
262         return getServiceMap(context).values();
263     }
264 
finishAccountManagerBlocker(AccountManagerFuture<?> future)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      */
setupAccountManagerAccount(final Context context, final Account account, final boolean email, final boolean calendar, final boolean contacts, final AccountManagerCallback<Bundle> callback)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      */
setupAccountManagerAccount(final Context context, final Account account, final boolean email, final boolean calendar, final boolean contacts, final HostAuth hostAuth, final AccountManagerCallback<Bundle> callback)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 
updateAccountManagerType(Context context, android.accounts.Account amAccount, final Map<String, String> protocolMap)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 
moveCalendarData(final ContentResolver resolver, final String name, final String oldType, final String newType)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 
moveContactsData(final ContentResolver resolver, final String name, final String oldType, final String newType)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      */
getServiceMap(final Context context)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 
547             try {
548                 final Resources res = context.getResources();
549                 final XmlResourceParser xml = res.getXml(R.xml.services);
550                 int xmlEventType;
551                 // walk through senders.xml file.
552                 while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) {
553                     if (xmlEventType == XmlResourceParser.START_TAG &&
554                             "emailservice".equals(xml.getName())) {
555                         final EmailServiceInfo info = new EmailServiceInfo();
556                         final TypedArray ta =
557                                 res.obtainAttributes(xml, R.styleable.EmailServiceInfo);
558                         info.protocol = ta.getString(R.styleable.EmailServiceInfo_protocol);
559                         info.accountType = ta.getString(R.styleable.EmailServiceInfo_accountType);
560                         info.name = ta.getString(R.styleable.EmailServiceInfo_name);
561                         info.hide = ta.getBoolean(R.styleable.EmailServiceInfo_hide, false);
562                         final String klass =
563                                 ta.getString(R.styleable.EmailServiceInfo_serviceClass);
564                         info.intentAction = ta.getString(R.styleable.EmailServiceInfo_intent);
565                         info.intentPackage =
566                                 ta.getString(R.styleable.EmailServiceInfo_intentPackage);
567                         info.defaultSsl =
568                                 ta.getBoolean(R.styleable.EmailServiceInfo_defaultSsl, false);
569                         info.port = ta.getInteger(R.styleable.EmailServiceInfo_port, 0);
570                         info.portSsl = ta.getInteger(R.styleable.EmailServiceInfo_portSsl, 0);
571                         info.offerTls = ta.getBoolean(R.styleable.EmailServiceInfo_offerTls, false);
572                         info.offerCerts =
573                                 ta.getBoolean(R.styleable.EmailServiceInfo_offerCerts, false);
574                         info.offerOAuth =
575                                 ta.getBoolean(R.styleable.EmailServiceInfo_offerOAuth, false);
576                         info.offerLocalDeletes =
577                             ta.getBoolean(R.styleable.EmailServiceInfo_offerLocalDeletes, false);
578                         info.defaultLocalDeletes =
579                             ta.getInteger(R.styleable.EmailServiceInfo_defaultLocalDeletes,
580                                     Account.DELETE_POLICY_ON_DELETE);
581                         info.offerPrefix =
582                             ta.getBoolean(R.styleable.EmailServiceInfo_offerPrefix, false);
583                         info.usesSmtp = ta.getBoolean(R.styleable.EmailServiceInfo_usesSmtp, false);
584                         info.usesAutodiscover =
585                             ta.getBoolean(R.styleable.EmailServiceInfo_usesAutodiscover, false);
586                         info.offerLookback =
587                             ta.getBoolean(R.styleable.EmailServiceInfo_offerLookback, false);
588                         info.defaultLookback =
589                             ta.getInteger(R.styleable.EmailServiceInfo_defaultLookback,
590                                     SyncWindow.SYNC_WINDOW_3_DAYS);
591                         info.syncChanges =
592                             ta.getBoolean(R.styleable.EmailServiceInfo_syncChanges, false);
593                         info.syncContacts =
594                             ta.getBoolean(R.styleable.EmailServiceInfo_syncContacts, false);
595                         info.syncCalendar =
596                             ta.getBoolean(R.styleable.EmailServiceInfo_syncCalendar, false);
597                         info.offerAttachmentPreload =
598                             ta.getBoolean(R.styleable.EmailServiceInfo_offerAttachmentPreload,
599                                     false);
600                         info.syncIntervalStrings =
601                             ta.getTextArray(R.styleable.EmailServiceInfo_syncIntervalStrings);
602                         info.syncIntervals =
603                             ta.getTextArray(R.styleable.EmailServiceInfo_syncIntervals);
604                         info.defaultSyncInterval =
605                             ta.getInteger(R.styleable.EmailServiceInfo_defaultSyncInterval, 15);
606                         info.inferPrefix = ta.getString(R.styleable.EmailServiceInfo_inferPrefix);
607                         info.offerLoadMore =
608                                 ta.getBoolean(R.styleable.EmailServiceInfo_offerLoadMore, false);
609                         info.offerMoveTo =
610                                 ta.getBoolean(R.styleable.EmailServiceInfo_offerMoveTo, false);
611                         info.requiresSetup =
612                                 ta.getBoolean(R.styleable.EmailServiceInfo_requiresSetup, false);
613                         info.isGmailStub =
614                                 ta.getBoolean(R.styleable.EmailServiceInfo_isGmailStub, false);
615 
616                         // Must have either "class" (local) or "intent" (remote)
617                         if (klass != null) {
618                             try {
619                                 // noinspection unchecked
620                                 info.klass = (Class<? extends Service>) Class.forName(klass);
621                             } catch (ClassNotFoundException e) {
622                                 throw new IllegalStateException(
623                                         "Class not found in service descriptor: " + klass);
624                             }
625                         }
626                         if (info.klass == null &&
627                                 info.intentAction == null &&
628                                 !info.isGmailStub) {
629                             throw new IllegalStateException(
630                                     "No class or intent action specified in service descriptor");
631                         }
632                         if (info.klass != null && info.intentAction != null) {
633                             throw new IllegalStateException(
634                                     "Both class and intent action specified in service descriptor");
635                         }
636                         builder.put(info.protocol, info);
637                     }
638                 }
639             } catch (XmlPullParserException e) {
640                 // ignore
641             } catch (IOException e) {
642                 // ignore
643             }
644             sServiceMap = builder.build();
645             return sServiceMap;
646         }
647     }
648 
649     /**
650      * Resolves a service name into a protocol name, or null if ambiguous
651      * @param context for loading service map
652      * @param accountType sync adapter service name
653      * @return protocol name or null
654      */
getProtocolFromAccountType(final Context context, final String accountType)655     public static @Nullable String getProtocolFromAccountType(final Context context,
656             final String accountType) {
657         if (TextUtils.isEmpty(accountType)) {
658             return null;
659         }
660         final Map <String, EmailServiceInfo> serviceInfoMap = getServiceMap(context);
661         String protocol = null;
662         for (final EmailServiceInfo info : serviceInfoMap.values()) {
663             if (TextUtils.equals(accountType, info.accountType)) {
664                 if (!TextUtils.isEmpty(protocol) && !TextUtils.equals(protocol, info.protocol)) {
665                     // More than one protocol matches
666                     return null;
667                 }
668                 protocol = info.protocol;
669             }
670         }
671         return protocol;
672     }
673 
asCalendarSyncAdapter(Uri uri, String account, String accountType)674     private static Uri asCalendarSyncAdapter(Uri uri, String account, String accountType) {
675         return uri.buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
676                 .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
677                 .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
678     }
679 
680     /**
681      * A no-op service that can be returned for non-existent/null protocols
682      */
683     class NullService implements IEmailService {
684         @Override
asBinder()685         public IBinder asBinder() {
686             return null;
687         }
688 
689         @Override
validate(HostAuthCompat hostauth)690         public Bundle validate(HostAuthCompat hostauth) throws RemoteException {
691             return null;
692         }
693 
694         @Override
loadAttachment(final IEmailServiceCallback cb, final long accountId, final long attachmentId, final boolean background)695         public void loadAttachment(final IEmailServiceCallback cb, final long accountId,
696                 final long attachmentId, final boolean background) throws RemoteException {
697         }
698 
699         @Override
updateFolderList(long accountId)700         public void updateFolderList(long accountId) throws RemoteException {}
701 
702         @Override
setLogging(int flags)703         public void setLogging(int flags) throws RemoteException {
704         }
705 
706         @Override
autoDiscover(String userName, String password)707         public Bundle autoDiscover(String userName, String password) throws RemoteException {
708             return null;
709         }
710 
711         @Override
sendMeetingResponse(long messageId, int response)712         public void sendMeetingResponse(long messageId, int response) throws RemoteException {
713         }
714 
715         @Override
deleteExternalAccountPIMData(final String emailAddress)716         public void deleteExternalAccountPIMData(final String emailAddress) throws RemoteException {
717         }
718 
719         @Override
searchMessages(long accountId, SearchParams params, long destMailboxId)720         public int searchMessages(long accountId, SearchParams params, long destMailboxId)
721                 throws RemoteException {
722             return 0;
723         }
724 
725         @Override
sendMail(long accountId)726         public void sendMail(long accountId) throws RemoteException {
727         }
728 
729         @Override
pushModify(long accountId)730         public void pushModify(long accountId) throws RemoteException {
731         }
732 
733         @Override
sync(final long accountId, final Bundle syncExtras)734         public int sync(final long accountId, final Bundle syncExtras) {
735             return EmailServiceStatus.SUCCESS;
736         }
737 
getApiVersion()738         public int getApiVersion() {
739             return EmailServiceVersion.CURRENT;
740         }
741     }
742 
setComponentStatus(final Context context, Class<?> clazz, boolean enabled)743     public static void setComponentStatus(final Context context, Class<?> clazz, boolean enabled) {
744         final ComponentName c = new ComponentName(context, clazz.getName());
745         context.getPackageManager().setComponentEnabledSetting(c,
746                 enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
747                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
748                 PackageManager.DONT_KILL_APP);
749     }
750 
751     /**
752      * This is a helper function that enables the proper Exchange component and disables
753      * the other Exchange component ensuring that only one is enabled at a time.
754      */
enableExchangeComponent(final Context context)755     public static void enableExchangeComponent(final Context context) {
756         if (VendorPolicyLoader.getInstance(context).useAlternateExchangeStrings()) {
757             LogUtils.d(LogUtils.TAG, "Enabling alternate EAS authenticator");
758             setComponentStatus(context, EasAuthenticatorServiceAlternate.class, true);
759             setComponentStatus(context, EasAuthenticatorService.class, false);
760         } else {
761             LogUtils.d(LogUtils.TAG, "Enabling EAS authenticator");
762             setComponentStatus(context, EasAuthenticatorService.class, true);
763             setComponentStatus(context,
764                     EasAuthenticatorServiceAlternate.class, false);
765         }
766     }
767 
disableExchangeComponents(final Context context)768     public static void disableExchangeComponents(final Context context) {
769         LogUtils.d(LogUtils.TAG, "Disabling EAS authenticators");
770         setComponentStatus(context, EasAuthenticatorServiceAlternate.class, false);
771         setComponentStatus(context, EasAuthenticatorService.class, false);
772     }
773 
774 }
775