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.HostAuth;
31import com.android.emailcommon.provider.Policy;
32
33import 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
50public 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