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