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