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