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 17package com.android.email.service; 18 19import android.accounts.AccountManager; 20import android.accounts.AccountManagerCallback; 21import android.accounts.AccountManagerFuture; 22import android.accounts.AuthenticatorException; 23import android.accounts.OperationCanceledException; 24import android.app.Service; 25import android.content.ComponentName; 26import android.content.ContentProviderClient; 27import android.content.ContentResolver; 28import android.content.ContentUris; 29import android.content.ContentValues; 30import android.content.Context; 31import android.content.Intent; 32import android.content.pm.ActivityInfo; 33import android.content.pm.PackageManager; 34import android.content.res.Configuration; 35import android.content.res.Resources; 36import android.content.res.TypedArray; 37import android.content.res.XmlResourceParser; 38import android.database.Cursor; 39import android.net.Uri; 40import android.os.Bundle; 41import android.os.IBinder; 42import android.os.RemoteException; 43import android.provider.CalendarContract; 44import android.provider.CalendarContract.Calendars; 45import android.provider.CalendarContract.SyncState; 46import android.provider.ContactsContract; 47import android.provider.ContactsContract.RawContacts; 48import android.provider.SyncStateContract; 49import android.support.annotation.Nullable; 50import android.text.TextUtils; 51 52import com.android.email.R; 53import com.android.emailcommon.VendorPolicyLoader; 54import com.android.emailcommon.provider.Account; 55import com.android.emailcommon.provider.EmailContent; 56import com.android.emailcommon.provider.EmailContent.AccountColumns; 57import com.android.emailcommon.provider.EmailContent.HostAuthColumns; 58import com.android.emailcommon.provider.HostAuth; 59import com.android.emailcommon.service.EmailServiceProxy; 60import com.android.emailcommon.service.EmailServiceStatus; 61import com.android.emailcommon.service.EmailServiceVersion; 62import com.android.emailcommon.service.HostAuthCompat; 63import com.android.emailcommon.service.IEmailService; 64import com.android.emailcommon.service.IEmailServiceCallback; 65import com.android.emailcommon.service.SearchParams; 66import com.android.emailcommon.service.ServiceProxy; 67import com.android.emailcommon.service.SyncWindow; 68import com.android.mail.utils.LogUtils; 69import com.google.common.collect.ImmutableMap; 70 71import org.xmlpull.v1.XmlPullParserException; 72 73import java.io.IOException; 74import java.util.Collection; 75import java.util.Map; 76 77/** 78 * Utility functions for EmailService support. 79 */ 80public 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 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 */ 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 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 685 public IBinder asBinder() { 686 return null; 687 } 688 689 @Override 690 public Bundle validate(HostAuthCompat hostauth) throws RemoteException { 691 return null; 692 } 693 694 @Override 695 public void loadAttachment(final IEmailServiceCallback cb, final long accountId, 696 final long attachmentId, final boolean background) throws RemoteException { 697 } 698 699 @Override 700 public void updateFolderList(long accountId) throws RemoteException {} 701 702 @Override 703 public void setLogging(int flags) throws RemoteException { 704 } 705 706 @Override 707 public Bundle autoDiscover(String userName, String password) throws RemoteException { 708 return null; 709 } 710 711 @Override 712 public void sendMeetingResponse(long messageId, int response) throws RemoteException { 713 } 714 715 @Override 716 public void deleteExternalAccountPIMData(final String emailAddress) throws RemoteException { 717 } 718 719 @Override 720 public int searchMessages(long accountId, SearchParams params, long destMailboxId) 721 throws RemoteException { 722 return 0; 723 } 724 725 @Override 726 public void sendMail(long accountId) throws RemoteException { 727 } 728 729 @Override 730 public void pushModify(long accountId) throws RemoteException { 731 } 732 733 @Override 734 public int sync(final long accountId, final Bundle syncExtras) { 735 return EmailServiceStatus.SUCCESS; 736 } 737 738 public int getApiVersion() { 739 return EmailServiceVersion.CURRENT; 740 } 741 } 742 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 */ 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 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