1 /* 2 * Copyright (C) 2009 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.emailcommon.service; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.os.Bundle; 22 import android.os.IBinder; 23 import android.os.RemoteException; 24 25 import com.android.emailcommon.Api; 26 import com.android.emailcommon.Device; 27 import com.android.emailcommon.TempDirectory; 28 import com.android.emailcommon.mail.MessagingException; 29 import com.android.emailcommon.provider.Account; 30 import com.android.emailcommon.provider.HostAuth; 31 import com.android.emailcommon.provider.Policy; 32 import com.android.mail.utils.LogUtils; 33 34 import java.io.IOException; 35 36 /** 37 * The EmailServiceProxy class provides a simple interface for the UI to call into the various 38 * EmailService classes (e.g. ExchangeService for EAS). It wraps the service connect/disconnect 39 * process so that the caller need not be concerned with it. 40 * 41 * Use the class like this: 42 * new EmailServiceProxy(context, class).loadAttachment(attachmentId, callback) 43 * 44 * Methods without a return value return immediately (i.e. are asynchronous); methods with a 45 * return value wait for a result from the Service (i.e. they should not be called from the UI 46 * thread) with a default timeout of 30 seconds (settable) 47 * 48 * An EmailServiceProxy object cannot be reused (trying to do so generates a RemoteException) 49 */ 50 51 public class EmailServiceProxy extends ServiceProxy implements IEmailService { 52 private static final String TAG = "EmailServiceProxy"; 53 54 public static final String AUTO_DISCOVER_BUNDLE_ERROR_CODE = "autodiscover_error_code"; 55 public static final String AUTO_DISCOVER_BUNDLE_HOST_AUTH = "autodiscover_host_auth"; 56 57 public static final String VALIDATE_BUNDLE_RESULT_CODE = "validate_result_code"; 58 public static final String VALIDATE_BUNDLE_POLICY_SET = "validate_policy_set"; 59 public static final String VALIDATE_BUNDLE_ERROR_MESSAGE = "validate_error_message"; 60 public static final String VALIDATE_BUNDLE_UNSUPPORTED_POLICIES = 61 "validate_unsupported_policies"; 62 public static final String VALIDATE_BUNDLE_PROTOCOL_VERSION = "validate_protocol_version"; 63 public static final String VALIDATE_BUNDLE_REDIRECT_ADDRESS = "validate_redirect_address"; 64 65 private Object mReturn = null; 66 private IEmailService mService; 67 private final boolean isRemote; 68 69 // Standard debugging 70 public static final int DEBUG_BIT = 1; 71 // Verbose (parser) logging 72 public static final int DEBUG_VERBOSE_BIT = 2; 73 // File (SD card) logging 74 public static final int DEBUG_FILE_BIT = 4; 75 // Enable strict mode 76 public static final int DEBUG_ENABLE_STRICT_MODE = 8; 77 78 // The first two constructors are used with local services that can be referenced by class 79 public EmailServiceProxy(Context _context, Class<?> _class) { 80 super(_context, new Intent(_context, _class)); 81 TempDirectory.setTempDirectory(_context); 82 isRemote = false; 83 } 84 85 // The following two constructors are used with remote services that must be referenced by 86 // a known action or by a prebuilt intent 87 public EmailServiceProxy(Context _context, Intent _intent) { 88 super(_context, _intent); 89 try { 90 Device.getDeviceId(_context); 91 } catch (IOException e) { 92 } 93 TempDirectory.setTempDirectory(_context); 94 isRemote = true; 95 } 96 97 @Override 98 public void onConnected(IBinder binder) { 99 mService = IEmailService.Stub.asInterface(binder); 100 } 101 102 public boolean isRemote() { 103 return isRemote; 104 } 105 106 @Override 107 public int getApiLevel() { 108 return Api.LEVEL; 109 } 110 111 /** 112 * Request an attachment to be loaded; the service MUST give higher priority to 113 * non-background loading. The service MUST use the loadAttachmentStatus callback when 114 * loading has started and stopped and SHOULD send callbacks with progress information if 115 * possible. 116 * 117 * @param cb The {@link IEmailServiceCallback} to use for this operation. 118 * @param attachmentId the id of the attachment record 119 * @param background whether or not this request corresponds to a background action (i.e. 120 * prefetch) vs a foreground action (user request) 121 */ 122 @Override 123 public void loadAttachment(final IEmailServiceCallback cb, final long attachmentId, 124 final boolean background) 125 throws RemoteException { 126 setTask(new ProxyTask() { 127 @Override 128 public void run() throws RemoteException { 129 try { 130 mService.loadAttachment(cb, attachmentId, background); 131 } catch (RemoteException e) { 132 try { 133 // Try to send a callback (if set) 134 if (cb != null) { 135 cb.loadAttachmentStatus(-1, attachmentId, 136 EmailServiceStatus.REMOTE_EXCEPTION, 0); 137 } 138 } catch (RemoteException e1) { 139 } 140 } 141 } 142 }, "loadAttachment"); 143 } 144 145 /** 146 * Request the sync of a mailbox; the service MUST send the syncMailboxStatus callback 147 * indicating "starting" and "finished" (or error), regardless of whether the mailbox is 148 * actually syncable. 149 * TODO: Remove this from IEmailService in favor of ContentResolver.requestSync. 150 * 151 * @param mailboxId the id of the mailbox record 152 * @param userRequest whether or not the user specifically asked for the sync 153 * @param deltaMessageCount amount by which to change the number of messages synced. 154 */ 155 @Deprecated 156 @Override 157 public void startSync(final long mailboxId, final boolean userRequest, 158 final int deltaMessageCount) throws RemoteException { 159 setTask(new ProxyTask() { 160 @Override 161 public void run() throws RemoteException { 162 mService.startSync(mailboxId, userRequest, deltaMessageCount); 163 } 164 }, "startSync"); 165 } 166 167 /** 168 * Request the immediate termination of a mailbox sync. Although the service is not required to 169 * acknowledge this request, it MUST send a "finished" (or error) syncMailboxStatus callback if 170 * the sync was started via the startSync service call. 171 * 172 * @param mailboxId the id of the mailbox record 173 */ 174 @Override 175 public void stopSync(final long mailboxId) throws RemoteException { 176 setTask(new ProxyTask() { 177 @Override 178 public void run() throws RemoteException { 179 mService.stopSync(mailboxId); 180 } 181 }, "stopSync"); 182 } 183 184 /** 185 * Validate a user account, given a protocol, host address, port, ssl status, and credentials. 186 * The result of this call is returned in a Bundle which MUST include a result code and MAY 187 * include a PolicySet that is required by the account. A successful validation implies a host 188 * address that serves the specified protocol and credentials sufficient to be authorized 189 * by the server to do so. 190 * 191 * @param hostAuth the hostauth object to validate 192 * @return a Bundle as described above 193 */ 194 @Override 195 public Bundle validate(final HostAuth hostAuth) throws RemoteException { 196 setTask(new ProxyTask() { 197 @Override 198 public void run() throws RemoteException{ 199 mReturn = mService.validate(hostAuth); 200 } 201 }, "validate"); 202 waitForCompletion(); 203 if (mReturn == null) { 204 Bundle bundle = new Bundle(); 205 bundle.putInt(VALIDATE_BUNDLE_RESULT_CODE, MessagingException.UNSPECIFIED_EXCEPTION); 206 return bundle; 207 } else { 208 Bundle bundle = (Bundle) mReturn; 209 bundle.setClassLoader(Policy.class.getClassLoader()); 210 LogUtils.v(TAG, "validate returns " + bundle.getInt(VALIDATE_BUNDLE_RESULT_CODE)); 211 return bundle; 212 } 213 } 214 215 /** 216 * Attempt to determine a user's host address and credentials from an email address and 217 * password. The result is returned in a Bundle which MUST include an error code and MAY (on 218 * success) include a HostAuth record sufficient to enable the service to validate the user's 219 * account. 220 * 221 * @param userName the user's email address 222 * @param password the user's password 223 * @return a Bundle as described above 224 */ 225 @Override 226 public Bundle autoDiscover(final String userName, final String password) 227 throws RemoteException { 228 setTask(new ProxyTask() { 229 @Override 230 public void run() throws RemoteException{ 231 mReturn = mService.autoDiscover(userName, password); 232 } 233 }, "autoDiscover"); 234 waitForCompletion(); 235 if (mReturn == null) { 236 return null; 237 } else { 238 Bundle bundle = (Bundle) mReturn; 239 bundle.setClassLoader(HostAuth.class.getClassLoader()); 240 LogUtils.v(TAG, "autoDiscover returns " 241 + bundle.getInt(AUTO_DISCOVER_BUNDLE_ERROR_CODE)); 242 return bundle; 243 } 244 } 245 246 /** 247 * Request that the service reload the folder list for the specified account. The service 248 * MUST use the syncMailboxListStatus callback to indicate "starting" and "finished" 249 * 250 * @param accountId the id of the account whose folder list is to be updated 251 */ 252 @Override 253 public void updateFolderList(final long accountId) throws RemoteException { 254 setTask(new ProxyTask() { 255 @Override 256 public void run() throws RemoteException { 257 mService.updateFolderList(accountId); 258 } 259 }, "updateFolderList"); 260 } 261 262 /** 263 * Specify the debug flags selected by the user. The service SHOULD log debug information as 264 * requested. 265 * 266 * @param flags an integer whose bits represent logging flags as defined in DEBUG_* flags above 267 */ 268 @Override 269 public void setLogging(final int flags) throws RemoteException { 270 setTask(new ProxyTask() { 271 @Override 272 public void run() throws RemoteException { 273 mService.setLogging(flags); 274 } 275 }, "setLogging"); 276 } 277 278 /** 279 * Alert the sync adapter that the account's host information has (or may have) changed; the 280 * service MUST stop all in-process or pending syncs, clear error states related to the 281 * account and its mailboxes, and restart necessary sync adapters (e.g. pushed mailboxes) 282 * 283 * @param accountId the id of the account whose host information has changed 284 */ 285 @Override 286 public void hostChanged(final long accountId) throws RemoteException { 287 setTask(new ProxyTask() { 288 @Override 289 public void run() throws RemoteException { 290 mService.hostChanged(accountId); 291 } 292 }, "hostChanged"); 293 } 294 295 /** 296 * Send a meeting response for the specified message 297 * 298 * @param messageId the id of the message containing the meeting request 299 * @param response the response code, as defined in EmailServiceConstants 300 */ 301 @Override 302 public void sendMeetingResponse(final long messageId, final int response) 303 throws RemoteException { 304 setTask(new ProxyTask() { 305 @Override 306 public void run() throws RemoteException { 307 mService.sendMeetingResponse(messageId, response); 308 } 309 }, "sendMeetingResponse"); 310 } 311 312 /** 313 * Request the sync adapter to load a complete message 314 * 315 * @param messageId the id of the message to be loaded 316 */ 317 @Override 318 public void loadMore(final long messageId) throws RemoteException { 319 setTask(new ProxyTask() { 320 @Override 321 public void run() throws RemoteException { 322 mService.loadMore(messageId); 323 } 324 }, "startSync"); 325 } 326 327 /** 328 * Not yet used 329 * 330 * @param accountId the account in which the folder is to be created 331 * @param name the name of the folder to be created 332 */ 333 @Override 334 public boolean createFolder(long accountId, String name) throws RemoteException { 335 return false; 336 } 337 338 /** 339 * Not yet used 340 * 341 * @param accountId the account in which the folder resides 342 * @param name the name of the folder to be deleted 343 */ 344 @Override 345 public boolean deleteFolder(long accountId, String name) throws RemoteException { 346 return false; 347 } 348 349 /** 350 * Not yet used 351 * 352 * @param accountId the account in which the folder resides 353 * @param oldName the name of the existing folder 354 * @param newName the new name for the folder 355 */ 356 @Override 357 public boolean renameFolder(long accountId, String oldName, String newName) 358 throws RemoteException { 359 return false; 360 } 361 362 /** 363 * Request the service to delete the account's PIM (personal information management) data. This 364 * data includes any data that is 1) associated with the account and 2) created/stored by the 365 * service or its sync adapters and 3) not stored in the EmailProvider database (e.g. contact 366 * and calendar information). 367 * 368 * @param emailAddress the email address for the account whose data should be deleted 369 */ 370 @Override 371 public void deleteAccountPIMData(final String emailAddress) throws RemoteException { 372 setTask(new ProxyTask() { 373 @Override 374 public void run() throws RemoteException { 375 mService.deleteAccountPIMData(emailAddress); 376 } 377 }, "deleteAccountPIMData"); 378 } 379 380 /** 381 * Search for messages given a query string. The string is interpreted as the logical AND of 382 * terms separated by white space. The search is performed on the specified mailbox in the 383 * specified account (including subfolders, as specified by the includeSubfolders parameter). 384 * At most numResults messages matching the query term(s) will be added to the mailbox specified 385 * as destMailboxId. If mailboxId is -1, the entire account will be searched. If firstResult is 386 * specified and non-zero, results will be added starting with the firstResult'th match (i.e. 387 * for the continuation of a previous search) 388 * 389 * @param accountId the id of the account to be searched 390 * @param searchParams the search specification 391 * @param destMailboxId the id of the mailbox into which search results are appended 392 * @return the total number of matches for this search (regardless of how many were requested) 393 */ 394 @Override 395 public int searchMessages(final long accountId, final SearchParams searchParams, 396 final long destMailboxId) throws RemoteException { 397 setTask(new ProxyTask() { 398 @Override 399 public void run() throws RemoteException{ 400 mReturn = mService.searchMessages(accountId, searchParams, destMailboxId); 401 } 402 }, "searchMessages"); 403 waitForCompletion(); 404 if (mReturn == null) { 405 return 0; 406 } else { 407 return (Integer)mReturn; 408 } 409 } 410 411 /** 412 * Request the service to send mail in the specified account's Outbox 413 * 414 * @param accountId the account whose outgoing mail should be sent 415 */ 416 @Override 417 public void sendMail(final long accountId) throws RemoteException { 418 setTask(new ProxyTask() { 419 @Override 420 public void run() throws RemoteException{ 421 mService.sendMail(accountId); 422 } 423 }, "sendMail"); 424 } 425 426 @Override 427 public int getCapabilities(final Account acct) throws RemoteException { 428 setTask(new ProxyTask() { 429 @Override 430 public void run() throws RemoteException{ 431 mReturn = mService.getCapabilities(acct); 432 } 433 }, "getCapabilities"); 434 waitForCompletion(); 435 if (mReturn == null) { 436 return 0; 437 } else { 438 return (Integer)mReturn; 439 } 440 } 441 /** 442 * Request that the account be updated for this service; this call is synchronous 443 * 444 * @param emailAddress the email address of the account to be updated 445 */ 446 @Override 447 public void serviceUpdated(final String emailAddress) throws RemoteException { 448 setTask(new ProxyTask() { 449 @Override 450 public void run() throws RemoteException{ 451 mService.serviceUpdated(emailAddress); 452 } 453 }, "settingsUpdate"); 454 waitForCompletion(); 455 } 456 457 458 @Override 459 public IBinder asBinder() { 460 return null; 461 } 462 } 463