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