PageRenderTime 1ms CodeModel.GetById 15ms app.highlight 11ms RepoModel.GetById 0ms app.codeStats 0ms

/src/com/elbenj/email/mail/store/ExchangeStore.java

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