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