ExchangeStore.java revision bcff14acf25d3a999b7448e317604e694c204f47
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.email.mail.store;
18
19import com.android.email.Email;
20import com.android.email.mail.AuthenticationFailedException;
21import com.android.email.mail.FetchProfile;
22import com.android.email.mail.Flag;
23import com.android.email.mail.Folder;
24import com.android.email.mail.Message;
25import com.android.email.mail.MessageRetrievalListener;
26import com.android.email.mail.MessagingException;
27import com.android.email.mail.Store;
28import com.android.email.mail.StoreSynchronizer;
29import com.android.email.provider.EmailContent.Account;
30import com.android.email.service.EasAuthenticatorService;
31import com.android.email.service.EmailServiceProxy;
32import com.android.exchange.Eas;
33import com.android.exchange.SyncManager;
34
35import android.accounts.AccountManager;
36import android.accounts.AuthenticatorException;
37import android.accounts.OperationCanceledException;
38import android.accounts.AccountManagerCallback;
39import android.accounts.AccountManagerFuture;
40import android.content.Context;
41import android.os.Bundle;
42import android.os.RemoteException;
43import android.text.TextUtils;
44import android.util.Log;
45
46import java.io.IOException;
47import java.net.URI;
48import java.net.URISyntaxException;
49import java.util.HashMap;
50
51/**
52 * This is a placeholder for use in Exchange implementations.  It is based on the notion of
53 * lightweight adapter classes for Store, Folder, and Sender, and a common facade for the common
54 * Transport code.
55 */
56public class ExchangeStore extends Store {
57    public static final String LOG_TAG = "ExchangeStore";
58
59    private final Context mContext;
60    private URI mUri;
61    private PersistentDataCallbacks mCallbacks;
62
63    private final ExchangeTransport mTransport;
64    private final HashMap<String, Folder> mFolders = new HashMap<String, Folder>();
65
66    /**
67     * Factory method.
68     */
69    public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks)
70    throws MessagingException {
71        return new ExchangeStore(uri, context, callbacks);
72    }
73
74    /**
75     * eas://user:password@server/domain
76     *
77     * @param _uri
78     * @param application
79     * @throws MessagingException
80     */
81    private ExchangeStore(String _uri, Context context, PersistentDataCallbacks callbacks)
82            throws MessagingException {
83        mContext = context;
84        try {
85            mUri = new URI(_uri);
86        } catch (URISyntaxException e) {
87            throw new MessagingException("Invalid uri for ExchangeStore");
88        }
89        mCallbacks = callbacks;
90
91        String scheme = mUri.getScheme();
92        int connectionSecurity;
93        if (scheme.equals("eas")) {
94            connectionSecurity = ExchangeTransport.CONNECTION_SECURITY_NONE;
95        } else if (scheme.equals("eas+ssl+")) {
96            connectionSecurity = ExchangeTransport.CONNECTION_SECURITY_SSL_REQUIRED;
97        } else {
98            throw new MessagingException("Unsupported protocol");
99        }
100
101        mTransport = ExchangeTransport.getInstance(mUri, context);
102    }
103
104    /**
105     * Retrieve the underlying transport.  Used primarily for testing.
106     * @return
107     */
108    /* package */ ExchangeTransport getTransport() {
109        return mTransport;
110    }
111
112    @Override
113    public void checkSettings() throws MessagingException {
114        mTransport.checkSettings(mUri);
115    }
116
117    static public void addSystemAccount(Context context, Account acct, boolean syncContacts) {
118        // Create a description of the new account
119        Bundle options = new Bundle();
120        options.putString(EasAuthenticatorService.OPTIONS_USERNAME, acct.mEmailAddress);
121        options.putString(EasAuthenticatorService.OPTIONS_PASSWORD, acct.mHostAuthRecv.mPassword);
122        options.putBoolean(EasAuthenticatorService.OPTIONS_CONTACTS_SYNC_ENABLED, syncContacts);
123
124        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
125            public void run(AccountManagerFuture<Bundle> future) {
126                try {
127                    Bundle bundle = future.getResult();
128                    bundle.keySet();
129                    Log.d(LOG_TAG, "account added: " + bundle);
130                } catch (OperationCanceledException e) {
131                    Log.d(LOG_TAG, "addAccount was canceled");
132                } catch (IOException e) {
133                    Log.d(LOG_TAG, "addAccount failed: " + e);
134                } catch (AuthenticatorException e) {
135                    Log.d(LOG_TAG, "addAccount failed: " + e);
136                }
137
138            }
139        };
140        // Here's where we tell AccountManager about the new account.  The addAccount
141        // method in AccountManager calls the addAccount method in our authenticator
142        // service (EasAuthenticatorService)
143        AccountManager.get(context).addAccount(Eas.ACCOUNT_MANAGER_TYPE, null, null,
144                options, null, callback, null);
145    }
146
147    @Override
148    public Folder getFolder(String name) throws MessagingException {
149        synchronized (mFolders) {
150            Folder folder = mFolders.get(name);
151            if (folder == null) {
152                folder = new ExchangeFolder(this, name);
153                mFolders.put(folder.getName(), folder);
154            }
155            return folder;
156        }
157    }
158
159    @Override
160    public Folder[] getPersonalNamespaces() throws MessagingException {
161        return new Folder[] {
162                getFolder(ExchangeTransport.FOLDER_INBOX),
163        };
164    }
165
166    /**
167     * Get class of SettingActivity for this Store class.
168     * @return Activity class that has class method actionEditIncomingSettings()
169     */
170    @Override
171    public Class<? extends android.app.Activity> getSettingActivityClass() {
172        return com.android.email.activity.setup.AccountSetupExchange.class;
173    }
174
175    /**
176     * Get class of sync'er for this Store class.  Because exchange Sync rules are so different
177     * than IMAP or POP3, it's likely that an Exchange implementation will need its own sync
178     * controller.  If so, this function must return a non-null value.
179     *
180     * @return Message Sync controller, or null to use default
181     */
182    @Override
183    public StoreSynchronizer getMessageSynchronizer() {
184        return null;
185    }
186
187    /**
188     * Inform MessagingController that this store requires message structures to be prefetched
189     * before it can fetch message bodies (this is due to EAS protocol restrictions.)
190     * @return always true for EAS
191     */
192    @Override
193    public boolean requireStructurePrefetch() {
194        return true;
195    }
196
197    /**
198     * Inform MessagingController that messages sent via EAS will be placed in the Sent folder
199     * automatically (server-side) and don't need to be uploaded.
200     * @return always false for EAS (assuming server-side copy is supported)
201     */
202    @Override
203    public boolean requireCopyMessageToSentFolder() {
204        return false;
205    }
206
207    public static class ExchangeTransport {
208        public static final int CONNECTION_SECURITY_NONE = 0;
209        public static final int CONNECTION_SECURITY_SSL_REQUIRED = 1;
210
211        public static final String FOLDER_INBOX = Email.INBOX;
212
213        private static final String TAG = "ExchangeTransport";
214        private final Context mContext;
215
216        private String mHost;
217        private String mDomain;
218        private String mUsername;
219        private String mPassword;
220
221        private static HashMap<String, ExchangeTransport> sUriToInstanceMap =
222            new HashMap<String, ExchangeTransport>();
223        private static final HashMap<String, Integer> sFolderMap = new HashMap<String, Integer>();
224
225        /**
226         * Public factory.  The transport should be a singleton (per Uri)
227         */
228        public synchronized static ExchangeTransport getInstance(URI uri, Context context)
229        throws MessagingException {
230            if (!uri.getScheme().equals("eas") && !uri.getScheme().equals("eas+ssl+")) {
231                throw new MessagingException("Invalid scheme");
232            }
233
234            final String key = uri.toString();
235            ExchangeTransport transport = sUriToInstanceMap.get(key);
236            if (transport == null) {
237                transport = new ExchangeTransport(uri, context);
238                sUriToInstanceMap.put(key, transport);
239            }
240            return transport;
241        }
242
243        /**
244         * Private constructor - use public factory.
245         */
246        private ExchangeTransport(URI uri, Context context) throws MessagingException {
247            mContext = context;
248            setUri(uri);
249        }
250
251        /**
252         * Use the Uri to set up a newly-constructed transport
253         * @param uri
254         * @throws MessagingException
255         */
256        private void setUri(final URI uri) throws MessagingException {
257            mHost = uri.getHost();
258            if (mHost == null) {
259                throw new MessagingException("host not specified");
260            }
261
262            mDomain = uri.getPath();
263            if (!TextUtils.isEmpty(mDomain)) {
264                mDomain = mDomain.substring(1);
265            }
266
267            final String userInfo = uri.getUserInfo();
268            if (userInfo == null) {
269                throw new MessagingException("user information not specifed");
270            }
271            final String[] uinfo = userInfo.split(":", 2);
272            if (uinfo.length != 2) {
273                throw new MessagingException("user name and password not specified");
274            }
275            mUsername = uinfo[0];
276            mPassword = uinfo[1];
277        }
278
279        /**
280         * Here's where we check the settings for EAS.
281         * @param uri the URI of the account to create
282         * @throws MessagingException if we can't authenticate the account
283         */
284        public void checkSettings(URI uri) throws MessagingException {
285            setUri(uri);
286            boolean ssl = uri.getScheme().contains("ssl+");
287            try {
288                int result = new EmailServiceProxy(mContext, SyncManager.class)
289                    .validate("eas", mHost, mUsername, mPassword, ssl ? 443 : 80, ssl);
290                if (result != MessagingException.NO_ERROR) {
291                    if (result == MessagingException.AUTHENTICATION_FAILED) {
292                        throw new AuthenticationFailedException("Authentication failed.");
293                    } else {
294                        throw new MessagingException(result);
295                    }
296                }
297            } catch (RemoteException e) {
298                throw new MessagingException("Call to validate generated an exception", e);
299            }
300        }
301
302        /**
303         * Typical helper function:  Return existence of a given folder
304         */
305        public boolean isFolderAvailable(final String folder) {
306            return sFolderMap.containsKey(folder);
307        }
308    }
309
310    public static class ExchangeFolder extends Folder {
311
312        private final ExchangeTransport mTransport;
313        @SuppressWarnings("unused")
314        private final ExchangeStore mStore;
315        @SuppressWarnings("unused")
316        private final String mName;
317
318        @SuppressWarnings("unused")
319        private PersistentDataCallbacks mPersistenceCallbacks;
320
321        public ExchangeFolder(ExchangeStore store, String name)
322                throws MessagingException {
323            mStore = store;
324            mTransport = store.getTransport();
325            mName = name;
326            if (!mTransport.isFolderAvailable(name)) {
327                throw new MessagingException("folder not supported: " + name);
328            }
329        }
330
331        @Override
332        public void appendMessages(Message[] messages) throws MessagingException {
333            // TODO Implement this function
334        }
335
336        @Override
337        public void close(boolean expunge) throws MessagingException {
338            mPersistenceCallbacks = null;
339            // TODO Implement this function
340        }
341
342        @Override
343        public void copyMessages(Message[] msgs, Folder folder, MessageUpdateCallbacks callbacks)
344                throws MessagingException {
345            // TODO Implement this function
346        }
347
348        @Override
349        public boolean create(FolderType type) throws MessagingException {
350            // TODO Implement this function
351            return false;
352        }
353
354        @Override
355        public void delete(boolean recurse) throws MessagingException {
356            // TODO Implement this function
357        }
358
359        @Override
360        public boolean exists() throws MessagingException {
361            // TODO Implement this function
362            return false;
363        }
364
365        @Override
366        public Message[] expunge() throws MessagingException {
367            // TODO Implement this function
368            return null;
369        }
370
371        @Override
372        public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener)
373                throws MessagingException {
374            // TODO Implement this function
375        }
376
377        @Override
378        public Message getMessage(String uid) throws MessagingException {
379            // TODO Implement this function
380            return null;
381        }
382
383        @Override
384        public int getMessageCount() throws MessagingException {
385            // TODO Implement this function
386            return 0;
387        }
388
389        @Override
390        public Message[] getMessages(int start, int end, MessageRetrievalListener listener)
391                throws MessagingException {
392            // TODO Implement this function
393            return null;
394        }
395
396        @Override
397        public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException {
398            // TODO Implement this function
399            return null;
400        }
401
402        @Override
403        public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
404                throws MessagingException {
405            // TODO Implement this function
406            return null;
407        }
408
409        @Override
410        public OpenMode getMode() throws MessagingException {
411            // TODO Implement this function
412            return null;
413        }
414
415        @Override
416        public String getName() {
417            // TODO Implement this function
418            return null;
419        }
420
421        @Override
422        public Flag[] getPermanentFlags() throws MessagingException {
423            // TODO Implement this function
424            return null;
425        }
426
427        @Override
428        public int getUnreadMessageCount() throws MessagingException {
429            // TODO Implement this function
430            return 0;
431        }
432
433        @Override
434        public boolean isOpen() {
435            // TODO Implement this function
436            return false;
437        }
438
439        @Override
440        public void open(OpenMode mode, PersistentDataCallbacks callbacks)
441                throws MessagingException {
442            mPersistenceCallbacks = callbacks;
443            // TODO Implement this function
444        }
445
446        @Override
447        public void setFlags(Message[] messages, Flag[] flags, boolean value)
448                throws MessagingException {
449            // TODO Implement this function
450        }
451
452        @Override
453        public Message createMessage(String uid) {
454            // TODO Auto-generated method stub
455            return null;
456        }
457    }
458
459
460}
461
462