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