/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.email.mail.store; import com.android.email.Email; import com.android.email.ExchangeUtils; import com.android.email.mail.AuthenticationFailedException; import com.android.email.mail.Folder; import com.android.email.mail.MessagingException; import com.android.email.mail.Store; import com.android.email.mail.StoreSynchronizer; import com.android.email.provider.EmailContent.Account; import com.android.email.service.EasAuthenticatorService; import android.accounts.AccountManager; import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.content.Context; import android.os.Bundle; import android.os.RemoteException; import android.text.TextUtils; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; /** * Our Exchange service does not use the sender/store model. This class exists for exactly two * purposes, (1) to provide a hook for checking account connections, and (2) to return * "AccountSetupExchange.class" for getSettingActivityClass(). */ public class ExchangeStore extends Store { public static final String LOG_TAG = "ExchangeStore"; private URI mUri; private final ExchangeTransport mTransport; /** * Factory method. */ public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks) throws MessagingException { return new ExchangeStore(uri, context, callbacks); } /** * eas://user:password@server/domain * * @param _uri * @param application */ private ExchangeStore(String _uri, Context context, PersistentDataCallbacks callbacks) throws MessagingException { try { mUri = new URI(_uri); } catch (URISyntaxException e) { throw new MessagingException("Invalid uri for ExchangeStore"); } mTransport = ExchangeTransport.getInstance(mUri, context); } @Override public void checkSettings() throws MessagingException { mTransport.checkSettings(mUri); } static public AccountManagerFuture<Bundle> addSystemAccount(Context context, Account acct, boolean syncContacts, boolean syncCalendar, AccountManagerCallback<Bundle> callback) { // Create a description of the new account Bundle options = new Bundle(); options.putString(EasAuthenticatorService.OPTIONS_USERNAME, acct.mEmailAddress); options.putString(EasAuthenticatorService.OPTIONS_PASSWORD, acct.mHostAuthRecv.mPassword); options.putBoolean(EasAuthenticatorService.OPTIONS_CONTACTS_SYNC_ENABLED, syncContacts); options.putBoolean(EasAuthenticatorService.OPTIONS_CALENDAR_SYNC_ENABLED, syncCalendar); // Here's where we tell AccountManager about the new account. The addAccount // method in AccountManager calls the addAccount method in our authenticator // service (EasAuthenticatorService) return AccountManager.get(context).addAccount(Email.EXCHANGE_ACCOUNT_MANAGER_TYPE, null, null, options, null, callback, null); } /** * Remove an account from the Account manager - see {@link AccountManager#removeAccount( * android.accounts.Account, AccountManagerCallback, android.os.Handler)}. * * @param context context to use * @param acct the account to remove * @param callback async results callback - pass null to use blocking mode */ static public AccountManagerFuture<Boolean> removeSystemAccount(Context context, Account acct, AccountManagerCallback<Bundle> callback) { android.accounts.Account systemAccount = new android.accounts.Account(acct.mEmailAddress, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE); return AccountManager.get(context).removeAccount(systemAccount, null, null); } @Override public Folder getFolder(String name) { return null; } @Override public Folder[] getPersonalNamespaces() { return null; } /** * Get class of SettingActivity for this Store class. * @return Activity class that has class method actionEditIncomingSettings() */ @Override public Class<? extends android.app.Activity> getSettingActivityClass() { return com.android.email.activity.setup.AccountSetupExchange.class; } /** * Get class of sync'er for this Store class. Because exchange Sync rules are so different * than IMAP or POP3, it's likely that an Exchange implementation will need its own sync * controller. If so, this function must return a non-null value. * * @return Message Sync controller, or null to use default */ @Override public StoreSynchronizer getMessageSynchronizer() { return null; } /** * Inform MessagingController that this store requires message structures to be prefetched * before it can fetch message bodies (this is due to EAS protocol restrictions.) * @return always true for EAS */ @Override public boolean requireStructurePrefetch() { return true; } /** * Inform MessagingController that messages sent via EAS will be placed in the Sent folder * automatically (server-side) and don't need to be uploaded. * @return always false for EAS (assuming server-side copy is supported) */ @Override public boolean requireCopyMessageToSentFolder() { return false; } public static class ExchangeTransport { private final Context mContext; private String mHost; private String mDomain; private String mUsername; private String mPassword; private static HashMap<String, ExchangeTransport> sUriToInstanceMap = new HashMap<String, ExchangeTransport>(); /** * Public factory. The transport should be a singleton (per Uri) */ public synchronized static ExchangeTransport getInstance(URI uri, Context context) throws MessagingException { if (!uri.getScheme().equals("eas") && !uri.getScheme().equals("eas+ssl+") && !uri.getScheme().equals("eas+ssl+trustallcerts")) { throw new MessagingException("Invalid scheme"); } final String key = uri.toString(); ExchangeTransport transport = sUriToInstanceMap.get(key); if (transport == null) { transport = new ExchangeTransport(uri, context); sUriToInstanceMap.put(key, transport); } return transport; } /** * Private constructor - use public factory. */ private ExchangeTransport(URI uri, Context context) throws MessagingException { mContext = context; setUri(uri); } /** * Use the Uri to set up a newly-constructed transport * @param uri * @throws MessagingException */ private void setUri(final URI uri) throws MessagingException { mHost = uri.getHost(); if (mHost == null) { throw new MessagingException("host not specified"); } mDomain = uri.getPath(); if (!TextUtils.isEmpty(mDomain)) { mDomain = mDomain.substring(1); } final String userInfo = uri.getUserInfo(); if (userInfo == null) { throw new MessagingException("user information not specifed"); } final String[] uinfo = userInfo.split(":", 2); if (uinfo.length != 2) { throw new MessagingException("user name and password not specified"); } mUsername = uinfo[0]; mPassword = uinfo[1]; } /** * Here's where we check the settings for EAS. * @param uri the URI of the account to create * @throws MessagingException if we can't authenticate the account */ public void checkSettings(URI uri) throws MessagingException { setUri(uri); boolean ssl = uri.getScheme().contains("+ssl"); boolean tssl = uri.getScheme().contains("+trustallcerts"); try { int port = ssl ? 443 : 80; int result = ExchangeUtils.getExchangeEmailService(mContext, null) .validate("eas", mHost, mUsername, mPassword, port, ssl, tssl); if (result != MessagingException.NO_ERROR) { if (result == MessagingException.AUTHENTICATION_FAILED) { throw new AuthenticationFailedException("Authentication failed."); } else { throw new MessagingException(result); } } } catch (RemoteException e) { throw new MessagingException("Call to validate generated an exception", e); } } } /** * We handle AutoDiscover for Exchange 2007 (and later) here, wrapping the EmailService call. * The service call returns a HostAuth and we return null if there was a service issue */ @Override public Bundle autoDiscover(Context context, String username, String password) throws MessagingException { try { return ExchangeUtils.getExchangeEmailService(context, null) .autoDiscover(username, password); } catch (RemoteException e) { return null; } } }