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.Api; 50 import com.android.emailcommon.Logging; 51 import com.android.emailcommon.provider.Account; 52 import com.android.emailcommon.provider.EmailContent; 53 import com.android.emailcommon.provider.EmailContent.AccountColumns; 54 import com.android.emailcommon.provider.HostAuth; 55 import com.android.emailcommon.service.EmailServiceProxy; 56 import com.android.emailcommon.service.IEmailService; 57 import com.android.emailcommon.service.IEmailServiceCallback; 58 import com.android.emailcommon.service.SearchParams; 59 import com.android.emailcommon.service.ServiceProxy; 60 import com.android.emailcommon.service.SyncWindow; 61 import com.android.mail.utils.LogUtils; 62 import com.google.common.collect.ImmutableMap; 63 64 import org.xmlpull.v1.XmlPullParserException; 65 66 import java.io.IOException; 67 import java.util.Collection; 68 import java.util.Map; 69 70 /** 71 * Utility functions for EmailService support. 72 */ 73 public 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 usesSmtp; 189 public boolean offerLocalDeletes; 190 public int defaultLocalDeletes; 191 public boolean offerPrefix; 192 public boolean usesAutodiscover; 193 public boolean offerLookback; 194 public int defaultLookback; 195 public boolean syncChanges; 196 public boolean syncContacts; 197 public boolean syncCalendar; 198 public boolean offerAttachmentPreload; 199 public CharSequence[] syncIntervalStrings; 200 public CharSequence[] syncIntervals; 201 public int defaultSyncInterval; 202 public String inferPrefix; 203 public boolean offerLoadMore; 204 public boolean offerMoveTo; 205 public boolean requiresSetup; 206 public boolean hide; 207 208 @Override 209 public String toString() { 210 StringBuilder sb = new StringBuilder("Protocol: "); 211 sb.append(protocol); 212 sb.append(", "); 213 sb.append(klass != null ? "Local" : "Remote"); 214 sb.append(" , Account Type: "); 215 sb.append(accountType); 216 return sb.toString(); 217 } 218 } 219 220 public static EmailServiceProxy getService(Context context, String protocol) { 221 EmailServiceInfo info = null; 222 // Handle the degenerate case here (account might have been deleted) 223 if (protocol != null) { 224 info = getServiceInfo(context, protocol); 225 } 226 if (info == null) { 227 LogUtils.w(Logging.LOG_TAG, "Returning NullService for " + protocol); 228 return new EmailServiceProxy(context, NullService.class); 229 } else { 230 return getServiceFromInfo(context, info); 231 } 232 } 233 234 public static EmailServiceProxy getServiceFromInfo(Context context, EmailServiceInfo info) { 235 if (info.klass != null) { 236 return new EmailServiceProxy(context, info.klass); 237 } else { 238 final Intent serviceIntent = getServiceIntent(info); 239 return new EmailServiceProxy(context, serviceIntent); 240 } 241 } 242 243 public static EmailServiceInfo getServiceInfoForAccount(Context context, long accountId) { 244 String protocol = Account.getProtocol(context, accountId); 245 return getServiceInfo(context, protocol); 246 } 247 248 public static EmailServiceInfo getServiceInfo(Context context, String protocol) { 249 return getServiceMap(context).get(protocol); 250 } 251 252 public static Collection<EmailServiceInfo> getServiceInfoList(Context context) { 253 return getServiceMap(context).values(); 254 } 255 256 private static void finishAccountManagerBlocker(AccountManagerFuture<?> future) { 257 try { 258 // Note: All of the potential errors are simply logged 259 // here, as there is nothing to actually do about them. 260 future.getResult(); 261 } catch (OperationCanceledException e) { 262 LogUtils.w(Logging.LOG_TAG, e.toString()); 263 } catch (AuthenticatorException e) { 264 LogUtils.w(Logging.LOG_TAG, e.toString()); 265 } catch (IOException e) { 266 LogUtils.w(Logging.LOG_TAG, e.toString()); 267 } 268 } 269 270 /** 271 * Add an account to the AccountManager. 272 * @param context Our {@link Context}. 273 * @param account The {@link Account} we're adding. 274 * @param email Whether the user wants to sync email on this account. 275 * @param calendar Whether the user wants to sync calendar on this account. 276 * @param contacts Whether the user wants to sync contacts on this account. 277 * @param callback A callback for when the AccountManager is done. 278 * @return The result of {@link AccountManager#addAccount}. 279 */ 280 public static AccountManagerFuture<Bundle> setupAccountManagerAccount(final Context context, 281 final Account account, final boolean email, final boolean calendar, 282 final boolean contacts, final AccountManagerCallback<Bundle> callback) { 283 final Bundle options = new Bundle(5); 284 final HostAuth hostAuthRecv = 285 HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv); 286 if (hostAuthRecv == null) { 287 return null; 288 } 289 // Set up username/password 290 options.putString(EasAuthenticatorService.OPTIONS_USERNAME, account.mEmailAddress); 291 options.putString(EasAuthenticatorService.OPTIONS_PASSWORD, hostAuthRecv.mPassword); 292 options.putBoolean(EasAuthenticatorService.OPTIONS_CONTACTS_SYNC_ENABLED, contacts); 293 options.putBoolean(EasAuthenticatorService.OPTIONS_CALENDAR_SYNC_ENABLED, calendar); 294 options.putBoolean(EasAuthenticatorService.OPTIONS_EMAIL_SYNC_ENABLED, email); 295 final EmailServiceInfo info = getServiceInfo(context, hostAuthRecv.mProtocol); 296 return AccountManager.get(context).addAccount(info.accountType, null, null, options, null, 297 callback, null); 298 } 299 300 public static void updateAccountManagerType(Context context, 301 android.accounts.Account amAccount, final Map<String, String> protocolMap) { 302 final ContentResolver resolver = context.getContentResolver(); 303 final Cursor c = resolver.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION, 304 AccountColumns.EMAIL_ADDRESS + "=?", new String[] { amAccount.name }, null); 305 // That's odd, isn't it? 306 if (c == null) return; 307 try { 308 if (c.moveToNext()) { 309 // Get the EmailProvider Account/HostAuth 310 final Account account = new Account(); 311 account.restore(c); 312 final HostAuth hostAuth = 313 HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv); 314 if (hostAuth == null) { 315 return; 316 } 317 318 final String newProtocol = protocolMap.get(hostAuth.mProtocol); 319 if (newProtocol == null) { 320 // This account doesn't need updating. 321 return; 322 } 323 324 LogUtils.w(Logging.LOG_TAG, "Converting " + amAccount.name + " to " 325 + 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(HostAuth.PROTOCOL, newProtocol); 340 resolver.update(ContentUris.withAppendedId(HostAuth.CONTENT_URI, hostAuth.mId), 341 hostValues, null, null); 342 LogUtils.w(Logging.LOG_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(Logging.LOG_TAG, "Email: " + email + ", Contacts: " + contacts + "," 358 + " Calendar: " + 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(Logging.LOG_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(Logging.LOG_TAG, "Get contacts key FAILED"); 384 } finally { 385 client.release(); 386 } 387 if (calendarSyncKey != null) { 388 LogUtils.w(Logging.LOG_TAG, "Got calendar key: " 389 + new String(calendarSyncKey)); 390 } 391 if (contactsSyncKey != null) { 392 LogUtils.w(Logging.LOG_TAG, "Got contacts key: " 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(Logging.LOG_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(Logging.LOG_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(Logging.LOG_TAG, "Set calendar key..."); 428 } catch (RemoteException e) { 429 LogUtils.w(Logging.LOG_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(Logging.LOG_TAG, "Set contacts key..."); 444 } catch (RemoteException e) { 445 LogUtils.w(Logging.LOG_TAG, "Set contacts key FAILED"); 446 } 447 } 448 449 // That's all folks! 450 LogUtils.w(Logging.LOG_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(Logging.LOG_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.offerLocalDeletes = 548 ta.getBoolean(R.styleable.EmailServiceInfo_offerLocalDeletes, false); 549 info.defaultLocalDeletes = 550 ta.getInteger(R.styleable.EmailServiceInfo_defaultLocalDeletes, 551 Account.DELETE_POLICY_ON_DELETE); 552 info.offerPrefix = 553 ta.getBoolean(R.styleable.EmailServiceInfo_offerPrefix, false); 554 info.usesSmtp = ta.getBoolean(R.styleable.EmailServiceInfo_usesSmtp, false); 555 info.usesAutodiscover = 556 ta.getBoolean(R.styleable.EmailServiceInfo_usesAutodiscover, false); 557 info.offerLookback = 558 ta.getBoolean(R.styleable.EmailServiceInfo_offerLookback, false); 559 info.defaultLookback = 560 ta.getInteger(R.styleable.EmailServiceInfo_defaultLookback, 561 SyncWindow.SYNC_WINDOW_3_DAYS); 562 info.syncChanges = 563 ta.getBoolean(R.styleable.EmailServiceInfo_syncChanges, false); 564 info.syncContacts = 565 ta.getBoolean(R.styleable.EmailServiceInfo_syncContacts, false); 566 info.syncCalendar = 567 ta.getBoolean(R.styleable.EmailServiceInfo_syncCalendar, false); 568 info.offerAttachmentPreload = 569 ta.getBoolean(R.styleable.EmailServiceInfo_offerAttachmentPreload, 570 false); 571 info.syncIntervalStrings = 572 ta.getTextArray(R.styleable.EmailServiceInfo_syncIntervalStrings); 573 info.syncIntervals = 574 ta.getTextArray(R.styleable.EmailServiceInfo_syncIntervals); 575 info.defaultSyncInterval = 576 ta.getInteger(R.styleable.EmailServiceInfo_defaultSyncInterval, 15); 577 info.inferPrefix = ta.getString(R.styleable.EmailServiceInfo_inferPrefix); 578 info.offerLoadMore = 579 ta.getBoolean(R.styleable.EmailServiceInfo_offerLoadMore, false); 580 info.offerMoveTo = 581 ta.getBoolean(R.styleable.EmailServiceInfo_offerMoveTo, false); 582 info.requiresSetup = 583 ta.getBoolean(R.styleable.EmailServiceInfo_requiresSetup, false); 584 585 // Must have either "class" (local) or "intent" (remote) 586 if (klass != null) { 587 try { 588 // noinspection unchecked 589 info.klass = (Class<? extends Service>) Class.forName(klass); 590 } catch (ClassNotFoundException e) { 591 throw new IllegalStateException( 592 "Class not found in service descriptor: " + klass); 593 } 594 } 595 if (info.klass == null && info.intentAction == null) { 596 throw new IllegalStateException( 597 "No class or intent action specified in service descriptor"); 598 } 599 if (info.klass != null && info.intentAction != null) { 600 throw new IllegalStateException( 601 "Both class and intent action specified in service descriptor"); 602 } 603 builder.put(info.protocol, info); 604 } 605 } 606 } catch (XmlPullParserException e) { 607 // ignore 608 } catch (IOException e) { 609 // ignore 610 } 611 sServiceMap = builder.build(); 612 return sServiceMap; 613 } 614 } 615 616 private static Uri asCalendarSyncAdapter(Uri uri, String account, String accountType) { 617 return uri.buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") 618 .appendQueryParameter(Calendars.ACCOUNT_NAME, account) 619 .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); 620 } 621 622 /** 623 * A no-op service that can be returned for non-existent/null protocols 624 */ 625 class NullService implements IEmailService { 626 @Override 627 public IBinder asBinder() { 628 return null; 629 } 630 631 @Override 632 public Bundle validate(HostAuth hostauth) throws RemoteException { 633 return null; 634 } 635 636 @Override 637 public void startSync(long mailboxId, boolean userRequest, int deltaMessageCount) 638 throws RemoteException { 639 } 640 641 @Override 642 public void stopSync(long mailboxId) throws RemoteException { 643 } 644 645 @Override 646 public void loadMore(long messageId) throws RemoteException { 647 } 648 649 @Override 650 public void loadAttachment(final IEmailServiceCallback cb, final long attachmentId, 651 final boolean background) throws RemoteException { 652 } 653 654 @Override 655 public void updateFolderList(long accountId) throws RemoteException { 656 } 657 658 @Override 659 public boolean createFolder(long accountId, String name) throws RemoteException { 660 return false; 661 } 662 663 @Override 664 public boolean deleteFolder(long accountId, String name) throws RemoteException { 665 return false; 666 } 667 668 @Override 669 public boolean renameFolder(long accountId, String oldName, String newName) 670 throws RemoteException { 671 return false; 672 } 673 674 @Override 675 public void setLogging(int on) throws RemoteException { 676 } 677 678 @Override 679 public void hostChanged(long accountId) throws RemoteException { 680 } 681 682 @Override 683 public Bundle autoDiscover(String userName, String password) throws RemoteException { 684 return null; 685 } 686 687 @Override 688 public void sendMeetingResponse(long messageId, int response) throws RemoteException { 689 } 690 691 @Override 692 public void deleteAccountPIMData(final String emailAddress) throws RemoteException { 693 } 694 695 @Override 696 public int getApiLevel() throws RemoteException { 697 return Api.LEVEL; 698 } 699 700 @Override 701 public int searchMessages(long accountId, SearchParams params, long destMailboxId) 702 throws RemoteException { 703 return 0; 704 } 705 706 @Override 707 public void sendMail(long accountId) throws RemoteException { 708 } 709 710 @Override 711 public void serviceUpdated(String emailAddress) throws RemoteException { 712 } 713 714 @Override 715 public int getCapabilities(Account acct) throws RemoteException { 716 return 0; 717 } 718 } 719 } 720