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