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