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