Home | History | Annotate | Download | only in service
      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