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.Device; 26 import com.android.emailcommon.TempDirectory; 27 import com.android.emailcommon.mail.MessagingException; 28 import com.android.emailcommon.provider.HostAuth; 29 import com.android.emailcommon.provider.Policy; 30 import com.android.mail.utils.LogUtils; 31 32 import java.io.IOException; 33 34 /** 35 * The EmailServiceProxy class provides a simple interface for the UI to call into the various 36 * EmailService classes (e.g. EasService for EAS). It wraps the service connect/disconnect 37 * process so that the caller need not be concerned with it. 38 * 39 * Use the class like this: 40 * new EmailServiceProxy(context, class).loadAttachment(attachmentId, callback) 41 * 42 * Methods without a return value return immediately (i.e. are asynchronous); methods with a 43 * return value wait for a result from the Service (i.e. they should not be called from the UI 44 * thread) with a default timeout of 30 seconds (settable) 45 * 46 * An EmailServiceProxy object cannot be reused (trying to do so generates a RemoteException) 47 */ 48 49 public class EmailServiceProxy extends ServiceProxy implements IEmailService { 50 private static final String TAG = "EmailServiceProxy"; 51 52 public static final String AUTO_DISCOVER_BUNDLE_ERROR_CODE = "autodiscover_error_code"; 53 public static final String AUTO_DISCOVER_BUNDLE_HOST_AUTH = "autodiscover_host_auth"; 54 55 public static final String VALIDATE_BUNDLE_RESULT_CODE = "validate_result_code"; 56 public static final String VALIDATE_BUNDLE_POLICY_SET = "validate_policy_set"; 57 public static final String VALIDATE_BUNDLE_ERROR_MESSAGE = "validate_error_message"; 58 public static final String VALIDATE_BUNDLE_UNSUPPORTED_POLICIES = 59 "validate_unsupported_policies"; 60 public static final String VALIDATE_BUNDLE_PROTOCOL_VERSION = "validate_protocol_version"; 61 public static final String VALIDATE_BUNDLE_REDIRECT_ADDRESS = "validate_redirect_address"; 62 63 private Object mReturn = null; 64 private IEmailService mService; 65 private final boolean isRemote; 66 67 // Standard debugging 68 public static final int DEBUG_BIT = 0x01; 69 // Verbose (parser) logging 70 public static final int DEBUG_EXCHANGE_BIT = 0x02; 71 // File (SD card) logging 72 public static final int DEBUG_FILE_BIT = 0x04; 73 // Enable strict mode 74 public static final int DEBUG_ENABLE_STRICT_MODE = 0x08; 75 76 // The first two constructors are used with local services that can be referenced by class 77 public EmailServiceProxy(Context _context, Class<?> _class) { 78 super(_context, new Intent(_context, _class)); 79 TempDirectory.setTempDirectory(_context); 80 isRemote = false; 81 } 82 83 // The following two constructors are used with remote services that must be referenced by 84 // a known action or by a prebuilt intent 85 public EmailServiceProxy(Context _context, Intent _intent) { 86 super(_context, _intent); 87 try { 88 Device.getDeviceId(_context); 89 } catch (IOException e) { 90 } 91 TempDirectory.setTempDirectory(_context); 92 isRemote = true; 93 } 94 95 @Override 96 public void onConnected(IBinder binder) { 97 mService = IEmailService.Stub.asInterface(binder); 98 } 99 100 public boolean isRemote() { 101 return isRemote; 102 } 103 104 /** 105 * Request an attachment to be loaded; the service MUST give higher priority to 106 * non-background loading. The service MUST use the loadAttachmentStatus callback when 107 * loading has started and stopped and SHOULD send callbacks with progress information if 108 * possible. 109 * 110 * @param cb The {@link IEmailServiceCallback} to use for this operation. 111 * @param accountId the id of the account in question 112 * @param attachmentId the id of the attachment record 113 * @param background whether or not this request corresponds to a background action (i.e. 114 * prefetch) vs a foreground action (user request) 115 */ 116 @Override 117 public void loadAttachment(final IEmailServiceCallback cb, final long accountId, 118 final long attachmentId, final boolean background) 119 throws RemoteException { 120 setTask(new ProxyTask() { 121 @Override 122 public void run() throws RemoteException { 123 try { 124 mService.loadAttachment(cb, accountId, attachmentId, background); 125 } catch (RemoteException e) { 126 try { 127 // Try to send a callback (if set) 128 if (cb != null) { 129 cb.loadAttachmentStatus(-1, attachmentId, 130 EmailServiceStatus.REMOTE_EXCEPTION, 0); 131 } 132 } catch (RemoteException e1) { 133 } 134 } 135 } 136 }, "loadAttachment"); 137 } 138 139 /** 140 * Validate a user account, given a protocol, host address, port, ssl status, and credentials. 141 * The result of this call is returned in a Bundle which MUST include a result code and MAY 142 * include a PolicySet that is required by the account. A successful validation implies a host 143 * address that serves the specified protocol and credentials sufficient to be authorized 144 * by the server to do so. 145 * 146 * @param hostAuthCom the hostAuthCom object to validate 147 * @return a Bundle as described above 148 */ 149 @Override 150 public Bundle validate(final HostAuthCompat hostAuthCom) throws RemoteException { 151 setTask(new ProxyTask() { 152 @Override 153 public void run() throws RemoteException{ 154 mReturn = mService.validate(hostAuthCom); 155 } 156 }, "validate"); 157 waitForCompletion(); 158 if (mReturn == null) { 159 Bundle bundle = new Bundle(); 160 bundle.putInt(VALIDATE_BUNDLE_RESULT_CODE, MessagingException.UNSPECIFIED_EXCEPTION); 161 return bundle; 162 } else { 163 Bundle bundle = (Bundle) mReturn; 164 bundle.setClassLoader(Policy.class.getClassLoader()); 165 LogUtils.v(TAG, "validate returns " + bundle.getInt(VALIDATE_BUNDLE_RESULT_CODE)); 166 return bundle; 167 } 168 } 169 170 /** 171 * Attempt to determine a user's host address and credentials from an email address and 172 * password. The result is returned in a Bundle which MUST include an error code and MAY (on 173 * success) include a HostAuth record sufficient to enable the service to validate the user's 174 * account. 175 * 176 * @param userName the user's email address 177 * @param password the user's password 178 * @return a Bundle as described above 179 */ 180 @Override 181 public Bundle autoDiscover(final String userName, final String password) 182 throws RemoteException { 183 setTask(new ProxyTask() { 184 @Override 185 public void run() throws RemoteException{ 186 mReturn = mService.autoDiscover(userName, password); 187 } 188 }, "autoDiscover"); 189 waitForCompletion(); 190 if (mReturn == null) { 191 return null; 192 } else { 193 Bundle bundle = (Bundle) mReturn; 194 bundle.setClassLoader(HostAuth.class.getClassLoader()); 195 LogUtils.v(TAG, "autoDiscover returns " 196 + bundle.getInt(AUTO_DISCOVER_BUNDLE_ERROR_CODE)); 197 return bundle; 198 } 199 } 200 201 /** 202 * Request that the service reload the folder list for the specified account. The service 203 * MUST use the syncMailboxListStatus callback to indicate "starting" and "finished" 204 * 205 * @param accountId the id of the account whose folder list is to be updated 206 */ 207 @Override 208 public void updateFolderList(final long accountId) throws RemoteException { 209 setTask(new ProxyTask() { 210 @Override 211 public void run() throws RemoteException { 212 mService.updateFolderList(accountId); 213 } 214 }, "updateFolderList"); 215 } 216 217 /** 218 * Specify the debug flags selected by the user. The service SHOULD log debug information as 219 * requested. 220 * 221 * @param flags an integer whose bits represent logging flags as defined in DEBUG_* flags above 222 */ 223 @Override 224 public void setLogging(final int flags) throws RemoteException { 225 setTask(new ProxyTask() { 226 @Override 227 public void run() throws RemoteException { 228 mService.setLogging(flags); 229 } 230 }, "setLogging"); 231 } 232 233 /** 234 * Send a meeting response for the specified message 235 * 236 * @param messageId the id of the message containing the meeting request 237 * @param response the response code, as defined in EmailServiceConstants 238 */ 239 @Override 240 public void sendMeetingResponse(final long messageId, final int response) 241 throws RemoteException { 242 setTask(new ProxyTask() { 243 @Override 244 public void run() throws RemoteException { 245 mService.sendMeetingResponse(messageId, response); 246 } 247 }, "sendMeetingResponse"); 248 } 249 250 /** 251 * Request the service to delete the account's PIM (personal information management) data. This 252 * data includes any data that is 1) associated with the account and 2) created/stored by the 253 * service or its sync adapters and 3) not stored in the EmailProvider database (e.g. contact 254 * and calendar information). 255 * 256 * @param emailAddress the email address for the account whose data should be deleted 257 */ 258 @Override 259 public void deleteExternalAccountPIMData(final String emailAddress) throws RemoteException { 260 setTask(new ProxyTask() { 261 @Override 262 public void run() throws RemoteException { 263 mService.deleteExternalAccountPIMData(emailAddress); 264 } 265 }, "deleteAccountPIMData"); 266 // This can be called when deleting accounts. After making this call, the caller will 267 // ask for account reconciliation, which will kill the processes. We wait for completion 268 // to avoid the race. 269 waitForCompletion(); 270 } 271 272 /** 273 * Search for messages given a query string. The string is interpreted as the logical AND of 274 * terms separated by white space. The search is performed on the specified mailbox in the 275 * specified account (including subfolders, as specified by the includeSubfolders parameter). 276 * At most numResults messages matching the query term(s) will be added to the mailbox specified 277 * as destMailboxId. If mailboxId is -1, the entire account will be searched. If firstResult is 278 * specified and non-zero, results will be added starting with the firstResult'th match (i.e. 279 * for the continuation of a previous search) 280 * 281 * @param accountId the id of the account to be searched 282 * @param searchParams the search specification 283 * @param destMailboxId the id of the mailbox into which search results are appended 284 * @return the total number of matches for this search (regardless of how many were requested) 285 */ 286 @Override 287 public int searchMessages(final long accountId, final SearchParams searchParams, 288 final long destMailboxId) throws RemoteException { 289 setTask(new ProxyTask() { 290 @Override 291 public void run() throws RemoteException{ 292 mReturn = mService.searchMessages(accountId, searchParams, destMailboxId); 293 } 294 }, "searchMessages"); 295 waitForCompletion(); 296 if (mReturn == null) { 297 return 0; 298 } else { 299 return (Integer) mReturn; 300 } 301 } 302 303 /** 304 * Request the service to send mail in the specified account's Outbox 305 * 306 * @param accountId the account whose outgoing mail should be sent. 307 */ 308 @Override 309 public void sendMail(final long accountId) throws RemoteException { 310 setTask(new ProxyTask() { 311 @Override 312 public void run() throws RemoteException{ 313 mService.sendMail(accountId); 314 } 315 }, "sendMail"); 316 } 317 318 /** 319 * Request the service to refresh its push notification status (e.g. to start or stop receiving 320 * them, or to change which folders we want notifications for). 321 * @param accountId The account whose push settings to modify. 322 */ 323 @Override 324 public void pushModify(final long accountId) throws RemoteException { 325 setTask(new ProxyTask() { 326 @Override 327 public void run() throws RemoteException{ 328 mService.pushModify(accountId); 329 } 330 }, "pushModify"); 331 } 332 333 @Override 334 public int sync(final long accountId, final Bundle syncExtras) { 335 setTask(new ProxyTask() { 336 @Override 337 public void run() throws RemoteException{ 338 mReturn = mService.sync(accountId, syncExtras); 339 } 340 }, "sync"); 341 waitForCompletion(); 342 if (mReturn == null) { 343 // This occurs if sync times out. 344 // TODO: Sync may take a long time, maybe we should extend the timeout here. 345 return EmailServiceStatus.IO_ERROR; 346 } else { 347 return (Integer)mReturn; 348 } 349 } 350 351 @Override 352 public IBinder asBinder() { 353 return null; 354 } 355 356 public int getApiVersion() { 357 setTask(new ProxyTask() { 358 @Override 359 public void run() throws RemoteException{ 360 mReturn = mService.getApiVersion(); 361 } 362 }, "getApiVersion"); 363 waitForCompletion(); 364 if (mReturn == null) { 365 // This occurs if there is a timeout or remote exception. Is not expected to happen. 366 LogUtils.wtf(TAG, "failed to get api version"); 367 return -1; 368 } else { 369 return (Integer) mReturn; 370 } 371 } 372 } 373