ExchangeStore.java revision f5418f1f93b02e7fab9f15eb201800b65510998e
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.emailcommon.mail.Folder;
22import com.android.emailcommon.mail.MessagingException;
23import com.android.emailcommon.provider.Account;
24import com.android.emailcommon.provider.EmailContent;
25import com.android.emailcommon.provider.HostAuth;
26import com.android.emailcommon.service.EmailServiceProxy;
27import com.android.emailcommon.service.IEmailService;
28
29import android.content.Context;
30import android.os.Bundle;
31import android.os.RemoteException;
32import android.text.TextUtils;
33
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    @SuppressWarnings("hiding")
44    private final ExchangeTransport mTransport;
45
46    /**
47     * Static named constructor.
48     */
49    public static Store newInstance(Account account, Context context,
50            PersistentDataCallbacks callbacks) throws MessagingException {
51        return new ExchangeStore(account, context, callbacks);
52    }
53
54    /**
55     * Creates a new store for the given account.
56     */
57    private ExchangeStore(Account account, Context context, PersistentDataCallbacks callbacks)
58            throws MessagingException {
59        mTransport = ExchangeTransport.getInstance(account, context);
60    }
61
62    @Override
63    public Bundle checkSettings() throws MessagingException {
64        return mTransport.checkSettings();
65    }
66
67    @Override
68    public Folder getFolder(String name) {
69        return null;
70    }
71
72    @Override
73    public Folder[] updateFolders() {
74        return null;
75    }
76
77    /**
78     * Get class of SettingActivity for this Store class.
79     * @return Activity class that has class method actionEditIncomingSettings()
80     */
81    @Override
82    public Class<? extends android.app.Activity> getSettingActivityClass() {
83        return com.android.email.activity.setup.AccountSetupExchange.class;
84    }
85
86    /**
87     * Inform MessagingController that messages sent via EAS will be placed in the Sent folder
88     * automatically (server-side) and don't need to be uploaded.
89     * @return always false for EAS (assuming server-side copy is supported)
90     */
91    @Override
92    public boolean requireCopyMessageToSentFolder() {
93        return false;
94    }
95
96    public static class ExchangeTransport {
97        private final Context mContext;
98        private String mHost;
99        private String mDomain;
100        private int mPort;
101        private boolean mSsl;
102        private boolean mTSsl;
103        private String mUsername;
104        private String mPassword;
105
106        private static final HashMap<Long, ExchangeTransport> sHostAuthToInstanceMap =
107            new HashMap<Long, ExchangeTransport>();
108
109        /**
110         * Public factory.  The transport should be a singleton
111         */
112        public synchronized static ExchangeTransport getInstance(Account account, Context context)
113                throws MessagingException {
114            HostAuth hostAuth = HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv);
115            if (hostAuth == null) {
116                hostAuth = new HostAuth();
117            }
118            final long storeKey = hostAuth.mId;
119            ExchangeTransport transport = sHostAuthToInstanceMap.get(storeKey);
120            if (transport == null) {
121                transport = new ExchangeTransport(account, context);
122                // Only cache a saved HostAuth key
123                if (storeKey != EmailContent.NOT_SAVED) {
124                    sHostAuthToInstanceMap.put(storeKey, transport);
125                }
126            }
127            return transport;
128        }
129
130        /**
131         * Private constructor - use public factory.
132         */
133        private ExchangeTransport(Account account, Context context) throws MessagingException {
134            mContext = context.getApplicationContext();
135            setAccount(account);
136        }
137
138        private void setAccount(final Account account) throws MessagingException {
139            HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext);
140            if (recvAuth == null || !STORE_SCHEME_EAS.equalsIgnoreCase(recvAuth.mProtocol)) {
141                throw new MessagingException("Unsupported protocol");
142            }
143            mHost = recvAuth.mAddress;
144            if (mHost == null) {
145                throw new MessagingException("host not specified");
146            }
147            mDomain = recvAuth.mDomain;
148            if (!TextUtils.isEmpty(mDomain)) {
149                mDomain = mDomain.substring(1);
150            }
151            mPort = 80;
152            if ((recvAuth.mFlags & HostAuth.FLAG_SSL) != 0) {
153                mPort = 443;
154                mSsl = true;
155            }
156            mTSsl = ((recvAuth.mFlags & HostAuth.FLAG_TRUST_ALL) != 0);
157
158            String[] userInfoParts = recvAuth.getLogin();
159            if (userInfoParts != null) {
160                mUsername = userInfoParts[0];
161                mPassword = userInfoParts[1];
162                if (TextUtils.isEmpty(mPassword)) {
163                    throw new MessagingException("user name and password not specified");
164                }
165            } else {
166                throw new MessagingException("user information not specifed");
167            }
168        }
169
170        /**
171         * Here's where we check the settings for EAS.
172         * @throws MessagingException if we can't authenticate the account
173         */
174        public Bundle checkSettings() throws MessagingException {
175            try {
176                IEmailService svc = ExchangeUtils.getExchangeService(mContext, null);
177                // Use a longer timeout for the validate command.  Note that the instanceof check
178                // shouldn't be necessary; we'll do it anyway, just to be safe
179                if (svc instanceof EmailServiceProxy) {
180                    ((EmailServiceProxy)svc).setTimeout(90);
181                }
182                return svc.validate("eas", mHost, mUsername, mPassword, mPort, mSsl, mTSsl);
183            } catch (RemoteException e) {
184                throw new MessagingException("Call to validate generated an exception", e);
185            }
186        }
187    }
188
189    /**
190     * We handle AutoDiscover for Exchange 2007 (and later) here, wrapping the EmailService call.
191     * The service call returns a HostAuth and we return null if there was a service issue
192     */
193    @Override
194    public Bundle autoDiscover(Context context, String username, String password) {
195        try {
196            return ExchangeUtils.getExchangeService(context, null).autoDiscover(username, password);
197        } catch (RemoteException e) {
198            return null;
199        }
200    }
201}
202