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.HostAuth;
23import com.android.emailcommon.provider.Policy;
24
25import android.content.Context;
26import android.content.Intent;
27import android.os.Bundle;
28import android.os.IBinder;
29import android.os.RemoteException;
30import android.util.Log;
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. ExchangeService 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    // Private intent that will be used to connect to an independent Exchange service
53    public static final String EXCHANGE_INTENT = "com.android.email.EXCHANGE_INTENT";
54
55    public static final String AUTO_DISCOVER_BUNDLE_ERROR_CODE = "autodiscover_error_code";
56    public static final String AUTO_DISCOVER_BUNDLE_HOST_AUTH = "autodiscover_host_auth";
57
58    public static final String VALIDATE_BUNDLE_RESULT_CODE = "validate_result_code";
59    public static final String VALIDATE_BUNDLE_POLICY_SET = "validate_policy_set";
60    public static final String VALIDATE_BUNDLE_ERROR_MESSAGE = "validate_error_message";
61    public static final String VALIDATE_BUNDLE_UNSUPPORTED_POLICIES =
62        "validate_unsupported_policies";
63
64    private final IEmailServiceCallback mCallback;
65    private Object mReturn = null;
66    private IEmailService mService;
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    }
86
87    // The following two constructors are used with remote services that must be referenced by
88    // a known action or by a prebuilt intent
89    public EmailServiceProxy(Context _context, Intent _intent, IEmailServiceCallback _callback) {
90        super(_context, _intent);
91        try {
92            Device.getDeviceId(_context);
93        } catch (IOException e) {
94        }
95        mCallback = _callback;
96    }
97
98    public EmailServiceProxy(Context _context, String _action, IEmailServiceCallback _callback) {
99        super(_context, new Intent(_action));
100        try {
101            Device.getDeviceId(_context);
102        } catch (IOException e) {
103        }
104        mCallback = _callback;
105    }
106
107    @Override
108    public void onConnected(IBinder binder) {
109        mService = IEmailService.Stub.asInterface(binder);
110    }
111
112    @Override
113    public int getApiLevel() {
114        return Api.LEVEL;
115    }
116
117    /**
118     * Request an attachment to be loaded; the service MUST give higher priority to
119     * non-background loading.  The service MUST use the loadAttachmentStatus callback when
120     * loading has started and stopped and SHOULD send callbacks with progress information if
121     * possible.
122     *
123     * @param attachmentId the id of the attachment record
124     * @param background whether or not this request corresponds to a background action (i.e.
125     * prefetch) vs a foreground action (user request)
126     */
127    public void loadAttachment(final long attachmentId, final boolean background)
128            throws RemoteException {
129        setTask(new ProxyTask() {
130            public void run() throws RemoteException {
131                try {
132                    if (mCallback != null) mService.setCallback(mCallback);
133                    mService.loadAttachment(attachmentId, background);
134                } catch (RemoteException e) {
135                    try {
136                        // Try to send a callback (if set)
137                        if (mCallback != null) {
138                            mCallback.loadAttachmentStatus(-1, attachmentId,
139                                    EmailServiceStatus.REMOTE_EXCEPTION, 0);
140                        }
141                    } catch (RemoteException e1) {
142                    }
143                }
144            }
145        }, "loadAttachment");
146    }
147
148    /**
149     * Request the sync of a mailbox; the service MUST send the syncMailboxStatus callback
150     * indicating "starting" and "finished" (or error), regardless of whether the mailbox is
151     * actually syncable.
152     *
153     * @param mailboxId the id of the mailbox record
154     * @param userRequest whether or not the user specifically asked for the sync
155     */
156    public void startSync(final long mailboxId, final boolean userRequest) throws RemoteException {
157        setTask(new ProxyTask() {
158            public void run() throws RemoteException {
159                if (mCallback != null) mService.setCallback(mCallback);
160                mService.startSync(mailboxId, userRequest);
161            }
162        }, "startSync");
163    }
164
165    /**
166     * Request the immediate termination of a mailbox sync. Although the service is not required to
167     * acknowledge this request, it MUST send a "finished" (or error) syncMailboxStatus callback if
168     * the sync was started via the startSync service call.
169     *
170     * @param mailboxId the id of the mailbox record
171     * @param userRequest whether or not the user specifically asked for the sync
172     */
173    public void stopSync(final long mailboxId) throws RemoteException {
174        setTask(new ProxyTask() {
175            public void run() throws RemoteException {
176                if (mCallback != null) mService.setCallback(mCallback);
177                mService.stopSync(mailboxId);
178            }
179        }, "stopSync");
180    }
181
182    /**
183     * Validate a user account, given a protocol, host address, port, ssl status, and credentials.
184     * The result of this call is returned in a Bundle which MUST include a result code and MAY
185     * include a PolicySet that is required by the account. A successful validation implies a host
186     * address that serves the specified protocol and credentials sufficient to be authorized
187     * by the server to do so.
188     *
189     * @param hostAuth the hostauth object to validate
190     * @return a Bundle as described above
191     */
192    public Bundle validate(final HostAuth hostAuth) throws RemoteException {
193        setTask(new ProxyTask() {
194            public void run() throws RemoteException{
195                if (mCallback != null) mService.setCallback(mCallback);
196                mReturn = mService.validate(hostAuth);
197            }
198        }, "validate");
199        waitForCompletion();
200        if (mReturn == null) {
201            Bundle bundle = new Bundle();
202            bundle.putInt(VALIDATE_BUNDLE_RESULT_CODE, MessagingException.UNSPECIFIED_EXCEPTION);
203            return bundle;
204        } else {
205            Bundle bundle = (Bundle) mReturn;
206            bundle.setClassLoader(Policy.class.getClassLoader());
207            Log.v(TAG, "validate returns " + bundle.getInt(VALIDATE_BUNDLE_RESULT_CODE));
208            return bundle;
209        }
210    }
211
212    /**
213     * Attempt to determine a user's host address and credentials from an email address and
214     * password. The result is returned in a Bundle which MUST include an error code and MAY (on
215     * success) include a HostAuth record sufficient to enable the service to validate the user's
216     * account.
217     *
218     * @param userName the user's email address
219     * @param password the user's password
220     * @return a Bundle as described above
221     */
222    public Bundle autoDiscover(final String userName, final String password)
223            throws RemoteException {
224        setTask(new ProxyTask() {
225            public void run() throws RemoteException{
226                if (mCallback != null) mService.setCallback(mCallback);
227                mReturn = mService.autoDiscover(userName, password);
228            }
229        }, "autoDiscover");
230        waitForCompletion();
231        if (mReturn == null) {
232            return null;
233        } else {
234            Bundle bundle = (Bundle) mReturn;
235            bundle.setClassLoader(HostAuth.class.getClassLoader());
236            Log.v(TAG, "autoDiscover returns " + bundle.getInt(AUTO_DISCOVER_BUNDLE_ERROR_CODE));
237            return bundle;
238        }
239    }
240
241    /**
242     * Request that the service reload the folder list for the specified account. The service
243     * MUST use the syncMailboxListStatus callback to indicate "starting" and "finished"
244     *
245     * @param accoundId the id of the account whose folder list is to be updated
246     */
247    public void updateFolderList(final long accountId) throws RemoteException {
248        setTask(new ProxyTask() {
249            public void run() throws RemoteException {
250                if (mCallback != null) mService.setCallback(mCallback);
251                mService.updateFolderList(accountId);
252            }
253        }, "updateFolderList");
254    }
255
256    /**
257     * Specify the debug flags selected by the user.  The service SHOULD log debug information as
258     * requested.
259     *
260     * @param flags an integer whose bits represent logging flags as defined in DEBUG_* flags above
261     */
262    public void setLogging(final int flags) throws RemoteException {
263        setTask(new ProxyTask() {
264            public void run() throws RemoteException {
265                if (mCallback != null) mService.setCallback(mCallback);
266                mService.setLogging(flags);
267            }
268        }, "setLogging");
269    }
270
271    /**
272     * Set the global callback object to be used by the service; the service MUST always use the
273     * most recently set callback object
274     *
275     * @param cb a callback object through which all service callbacks are executed
276     */
277    public void setCallback(final IEmailServiceCallback cb) throws RemoteException {
278        setTask(new ProxyTask() {
279            public void run() throws RemoteException {
280                mService.setCallback(cb);
281            }
282        }, "setCallback");
283    }
284
285    /**
286     * Alert the sync adapter that the account's host information has (or may have) changed; the
287     * service MUST stop all in-process or pending syncs, clear error states related to the
288     * account and its mailboxes, and restart necessary sync adapters (e.g. pushed mailboxes)
289     *
290     * @param accountId the id of the account whose host information has changed
291     */
292    public void hostChanged(final long accountId) throws RemoteException {
293        setTask(new ProxyTask() {
294            public void run() throws RemoteException {
295                mService.hostChanged(accountId);
296            }
297        }, "hostChanged");
298    }
299
300    /**
301     * Send a meeting response for the specified message
302     *
303     * @param messageId the id of the message containing the meeting request
304     * @param response the response code, as defined in EmailServiceConstants
305     */
306    public void sendMeetingResponse(final long messageId, final int response)
307            throws RemoteException {
308        setTask(new ProxyTask() {
309            public void run() throws RemoteException {
310                if (mCallback != null) mService.setCallback(mCallback);
311                mService.sendMeetingResponse(messageId, response);
312            }
313        }, "sendMeetingResponse");
314    }
315
316    /**
317     * Not yet used; intended to request the sync adapter to load a complete message
318     *
319     * @param messageId the id of the message to be loaded
320     */
321    public void loadMore(long messageId) throws RemoteException {
322    }
323
324    /**
325     * Not yet used
326     *
327     * @param accountId the account in which the folder is to be created
328     * @param name the name of the folder to be created
329    */
330    public boolean createFolder(long accountId, String name) throws RemoteException {
331        return false;
332    }
333
334    /**
335     * Not yet used
336     *
337     * @param accountId the account in which the folder resides
338     * @param name the name of the folder to be deleted
339     */
340    public boolean deleteFolder(long accountId, String name) throws RemoteException {
341        return false;
342    }
343
344    /**
345     * Not yet used
346     *
347     * @param accountId the account in which the folder resides
348     * @param oldName the name of the existing folder
349     * @param newName the new name for the folder
350     */
351    public boolean renameFolder(long accountId, String oldName, String newName)
352            throws RemoteException {
353        return false;
354    }
355
356    /**
357     * Request the service to delete the account's PIM (personal information management) data. This
358     * data includes any data that is 1) associated with the account and 2) created/stored by the
359     * service or its sync adapters and 3) not stored in the EmailProvider database (e.g. contact
360     * and calendar information).
361     *
362     * @param accountId the account whose data is to be deleted
363     */
364    public void deleteAccountPIMData(final long accountId) throws RemoteException {
365        setTask(new ProxyTask() {
366            public void run() throws RemoteException {
367                mService.deleteAccountPIMData(accountId);
368            }
369        }, "deleteAccountPIMData");
370    }
371
372
373    /**
374     * PRELIMINARY
375     * Search for messages given a query string.  The string is interpreted as the logical AND of
376     * terms separated by white space.  The search is performed on the specified mailbox in the
377     * specified account (including subfolders, as specified by the includeSubfolders parameter).
378     * At most numResults messages matching the query term(s) will be added to the mailbox specified
379     * as destMailboxId. If mailboxId is -1, the entire account will be searched. If firstResult is
380     * specified and non-zero, results will be added starting with the firstResult'th match (i.e.
381     * for the continuation of a previous search)
382     *
383     * @param accountId the id of the account to be searched
384     * @param searchParams the search specification
385     * @param destMailboxId the id of the mailbox into which search results are appended
386     * @return the total number of matches for this search (regardless of how many were requested)
387     */
388    public int searchMessages(final long accountId, final SearchParams searchParams,
389            final long destMailboxId) throws RemoteException {
390        setTask(new ProxyTask() {
391            public void run() throws RemoteException{
392                if (mCallback != null) mService.setCallback(mCallback);
393                mReturn = mService.searchMessages(accountId, searchParams, destMailboxId);
394            }
395        }, "searchMessages");
396        waitForCompletion();
397        if (mReturn == null) {
398            return 0;
399        } else {
400            return (Integer)mReturn;
401        }
402    }
403    public IBinder asBinder() {
404        return null;
405    }
406}
407