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