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