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 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