• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.email.mail.store;
18 
19 import com.android.email.Email;
20 import com.android.email.ExchangeUtils;
21 import com.android.email.mail.AuthenticationFailedException;
22 import com.android.email.mail.Folder;
23 import com.android.email.mail.MessagingException;
24 import com.android.email.mail.Store;
25 import com.android.email.mail.StoreSynchronizer;
26 import com.android.email.provider.EmailContent.Account;
27 import com.android.email.service.EasAuthenticatorService;
28 import com.android.email.service.EmailServiceProxy;
29 import com.android.email.service.IEmailService;
30 
31 import android.accounts.AccountManager;
32 import android.accounts.AccountManagerCallback;
33 import android.accounts.AccountManagerFuture;
34 import android.content.Context;
35 import android.os.Bundle;
36 import android.os.RemoteException;
37 import android.text.TextUtils;
38 
39 import java.net.URI;
40 import java.net.URISyntaxException;
41 import 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  */
48 public 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      */
newInstance(String uri, Context context, PersistentDataCallbacks callbacks)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      */
ExchangeStore(String _uri, Context context, PersistentDataCallbacks callbacks)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
checkSettings()80     public void checkSettings() throws MessagingException {
81         mTransport.checkSettings(mUri);
82     }
83 
addSystemAccount(Context context, Account acct, boolean syncContacts, boolean syncCalendar, AccountManagerCallback<Bundle> callback)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      */
removeSystemAccount(Context context, Account acct, AccountManagerCallback<Bundle> callback)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
getFolder(String name)116     public Folder getFolder(String name) {
117         return null;
118     }
119 
120     @Override
getPersonalNamespaces()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
getSettingActivityClass()130     public Class<? extends android.app.Activity> getSettingActivityClass() {
131         return com.android.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
getMessageSynchronizer()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
requireStructurePrefetch()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
requireCopyMessageToSentFolder()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          */
getInstance(URI uri, Context context)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          */
ExchangeTransport(URI uri, Context context)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          */
setUri(final URI uri)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          */
checkSettings(URI uri)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
autoDiscover(Context context, String username, String password)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 }
279