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 EmailServiceProxy(Context _context, Class<?> _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 EmailServiceProxy(Context _context, Intent _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 onConnected(IBinder binder)96 public void onConnected(IBinder binder) { 97 mService = IEmailService.Stub.asInterface(binder); 98 } 99 isRemote()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 loadAttachment(final IEmailServiceCallback cb, final long accountId, final long attachmentId, final boolean background)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 validate(final HostAuthCompat hostAuthCom)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 autoDiscover(final String userName, final String password)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 updateFolderList(final long accountId)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 setLogging(final int flags)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 sendMeetingResponse(final long messageId, final int response)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 deleteExternalAccountPIMData(final String emailAddress)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 searchMessages(final long accountId, final SearchParams searchParams, final long destMailboxId)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 sendMail(final long accountId)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 pushModify(final long accountId)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 sync(final long accountId, final Bundle syncExtras)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 asBinder()352 public IBinder asBinder() { 353 return null; 354 } 355 getApiVersion()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