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