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