ExchangeStore.java revision 17da1767e396b873723d53b2aef93da8aca2c00e
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.mail.AuthenticationFailedException;
20import com.android.email.mail.Folder;
21import com.android.email.mail.MessagingException;
22import com.android.email.mail.Store;
23import com.android.email.mail.StoreSynchronizer;
24import com.android.email.provider.EmailContent.Account;
25import com.android.email.service.EasAuthenticatorService;
26import com.android.email.service.EmailServiceProxy;
27import com.android.exchange.Eas;
28import com.android.exchange.SyncManager;
29
30import android.accounts.AccountManager;
31import android.accounts.AccountManagerCallback;
32import android.content.Context;
33import android.os.Bundle;
34import android.os.RemoteException;
35import android.text.TextUtils;
36
37import java.net.URI;
38import java.net.URISyntaxException;
39import java.util.HashMap;
40
41/**
42 * Our Exchange service does not use the sender/store model.  This class exists for exactly two
43 * purposes, (1) to provide a hook for checking account connections, and (2) to return
44 * "AccountSetupExchange.class" for getSettingActivityClass().
45 */
46public class ExchangeStore extends Store {
47    public static final String LOG_TAG = "ExchangeStore";
48
49    private URI mUri;
50    private final ExchangeTransport mTransport;
51
52    /**
53     * Factory method.
54     */
55    public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks)
56            throws MessagingException {
57        return new ExchangeStore(uri, context, callbacks);
58    }
59
60    /**
61     * eas://user:password@server/domain
62     *
63     * @param _uri
64     * @param application
65     */
66    private ExchangeStore(String _uri, Context context, PersistentDataCallbacks callbacks)
67            throws MessagingException {
68        try {
69            mUri = new URI(_uri);
70        } catch (URISyntaxException e) {
71            throw new MessagingException("Invalid uri for ExchangeStore");
72        }
73
74        mTransport = ExchangeTransport.getInstance(mUri, context);
75    }
76
77    @Override
78    public void checkSettings() throws MessagingException {
79        mTransport.checkSettings(mUri);
80    }
81
82    static public void addSystemAccount(Context context, Account acct, boolean syncContacts,
83            AccountManagerCallback<Bundle> callback) {
84        // Create a description of the new account
85        Bundle options = new Bundle();
86        options.putString(EasAuthenticatorService.OPTIONS_USERNAME, acct.mEmailAddress);
87        options.putString(EasAuthenticatorService.OPTIONS_PASSWORD, acct.mHostAuthRecv.mPassword);
88        options.putBoolean(EasAuthenticatorService.OPTIONS_CONTACTS_SYNC_ENABLED, syncContacts);
89
90        // Here's where we tell AccountManager about the new account.  The addAccount
91        // method in AccountManager calls the addAccount method in our authenticator
92        // service (EasAuthenticatorService)
93        AccountManager.get(context).addAccount(Eas.ACCOUNT_MANAGER_TYPE, null, null,
94                options, null, callback, null);
95    }
96
97    @Override
98    public Folder getFolder(String name) {
99        return null;
100    }
101
102    @Override
103    public Folder[] getPersonalNamespaces() {
104        return null;
105    }
106
107    /**
108     * Get class of SettingActivity for this Store class.
109     * @return Activity class that has class method actionEditIncomingSettings()
110     */
111    @Override
112    public Class<? extends android.app.Activity> getSettingActivityClass() {
113        return com.android.email.activity.setup.AccountSetupExchange.class;
114    }
115
116    /**
117     * Get class of sync'er for this Store class.  Because exchange Sync rules are so different
118     * than IMAP or POP3, it's likely that an Exchange implementation will need its own sync
119     * controller.  If so, this function must return a non-null value.
120     *
121     * @return Message Sync controller, or null to use default
122     */
123    @Override
124    public StoreSynchronizer getMessageSynchronizer() {
125        return null;
126    }
127
128    /**
129     * Inform MessagingController that this store requires message structures to be prefetched
130     * before it can fetch message bodies (this is due to EAS protocol restrictions.)
131     * @return always true for EAS
132     */
133    @Override
134    public boolean requireStructurePrefetch() {
135        return true;
136    }
137
138    /**
139     * Inform MessagingController that messages sent via EAS will be placed in the Sent folder
140     * automatically (server-side) and don't need to be uploaded.
141     * @return always false for EAS (assuming server-side copy is supported)
142     */
143    @Override
144    public boolean requireCopyMessageToSentFolder() {
145        return false;
146    }
147
148    public static class ExchangeTransport {
149        private final Context mContext;
150
151        private String mHost;
152        private String mDomain;
153        private String mUsername;
154        private String mPassword;
155
156        private static HashMap<String, ExchangeTransport> sUriToInstanceMap =
157            new HashMap<String, ExchangeTransport>();
158
159        /**
160         * Public factory.  The transport should be a singleton (per Uri)
161         */
162        public synchronized static ExchangeTransport getInstance(URI uri, Context context)
163        throws MessagingException {
164            if (!uri.getScheme().equals("eas") && !uri.getScheme().equals("eas+ssl+") &&
165                    !uri.getScheme().equals("eas+ssl+trustallcerts")) {
166                throw new MessagingException("Invalid scheme");
167            }
168
169            final String key = uri.toString();
170            ExchangeTransport transport = sUriToInstanceMap.get(key);
171            if (transport == null) {
172                transport = new ExchangeTransport(uri, context);
173                sUriToInstanceMap.put(key, transport);
174            }
175            return transport;
176        }
177
178        /**
179         * Private constructor - use public factory.
180         */
181        private ExchangeTransport(URI uri, Context context) throws MessagingException {
182            mContext = context;
183            setUri(uri);
184        }
185
186        /**
187         * Use the Uri to set up a newly-constructed transport
188         * @param uri
189         * @throws MessagingException
190         */
191        private void setUri(final URI uri) throws MessagingException {
192            mHost = uri.getHost();
193            if (mHost == null) {
194                throw new MessagingException("host not specified");
195            }
196
197            mDomain = uri.getPath();
198            if (!TextUtils.isEmpty(mDomain)) {
199                mDomain = mDomain.substring(1);
200            }
201
202            final String userInfo = uri.getUserInfo();
203            if (userInfo == null) {
204                throw new MessagingException("user information not specifed");
205            }
206            final String[] uinfo = userInfo.split(":", 2);
207            if (uinfo.length != 2) {
208                throw new MessagingException("user name and password not specified");
209            }
210            mUsername = uinfo[0];
211            mPassword = uinfo[1];
212        }
213
214        /**
215         * Here's where we check the settings for EAS.
216         * @param uri the URI of the account to create
217         * @throws MessagingException if we can't authenticate the account
218         */
219        public void checkSettings(URI uri) throws MessagingException {
220            setUri(uri);
221            boolean ssl = uri.getScheme().contains("+ssl");
222            boolean tssl = uri.getScheme().contains("+trustallcerts");
223            try {
224                int port = ssl ? 443 : 80;
225                int result = new EmailServiceProxy(mContext, SyncManager.class)
226                    .validate("eas", mHost, mUsername, mPassword, port, ssl, tssl);
227                if (result != MessagingException.NO_ERROR) {
228                    if (result == MessagingException.AUTHENTICATION_FAILED) {
229                        throw new AuthenticationFailedException("Authentication failed.");
230                    } else {
231                        throw new MessagingException(result);
232                    }
233                }
234            } catch (RemoteException e) {
235                throw new MessagingException("Call to validate generated an exception", e);
236            }
237        }
238    }
239
240    /**
241     * We handle AutoDiscover for Exchange 2007 (and later) here, wrapping the EmailService call.
242     * The service call returns a HostAuth and we return null if there was a service issue
243     */
244    @Override
245    public Bundle autoDiscover(Context context, String username, String password)
246            throws MessagingException {
247        try {
248            return new EmailServiceProxy(context, SyncManager.class)
249                .autoDiscover(username, password);
250        } catch (RemoteException e) {
251            return null;
252        }
253    }
254}
255
256