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