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