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;
24
25import com.android.emailcommon.Device;
26import com.android.emailcommon.TempDirectory;
27import com.android.emailcommon.mail.MessagingException;
28import com.android.emailcommon.provider.HostAuth;
29import com.android.emailcommon.provider.Policy;
30import com.android.mail.utils.LogUtils;
31
32import 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
49public 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 = 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        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