ExchangeStore.java revision 46d7d7f1b6387d144c3f9e7c987418dc8f55fad4
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.mail.AuthenticationFailedException; 21import com.android.email.mail.FetchProfile; 22import com.android.email.mail.Flag; 23import com.android.email.mail.Folder; 24import com.android.email.mail.Message; 25import com.android.email.mail.MessageRetrievalListener; 26import com.android.email.mail.MessagingException; 27import com.android.email.mail.Store; 28import com.android.email.mail.StoreSynchronizer; 29import com.android.email.provider.EmailContent.Account; 30import com.android.email.service.EasAuthenticatorService; 31import com.android.email.service.EmailServiceProxy; 32import com.android.exchange.Eas; 33import com.android.exchange.SyncManager; 34 35import android.accounts.AccountManager; 36import android.accounts.AuthenticatorException; 37import android.accounts.OperationCanceledException; 38import android.accounts.AccountManagerCallback; 39import android.accounts.AccountManagerFuture; 40import android.content.Context; 41import android.os.Bundle; 42import android.os.RemoteException; 43import android.text.TextUtils; 44import android.util.Log; 45 46import java.io.IOException; 47import java.net.URI; 48import java.net.URISyntaxException; 49import java.util.HashMap; 50 51/** 52 * This is a placeholder for use in Exchange implementations. It is based on the notion of 53 * lightweight adapter classes for Store, Folder, and Sender, and a common facade for the common 54 * Transport code. 55 */ 56public class ExchangeStore extends Store { 57 public static final String LOG_TAG = "ExchangeStore"; 58 59 private final Context mContext; 60 private URI mUri; 61 private PersistentDataCallbacks mCallbacks; 62 63 private final ExchangeTransport mTransport; 64 private final HashMap<String, Folder> mFolders = new HashMap<String, Folder>(); 65 66 /** 67 * Factory method. 68 */ 69 public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks) 70 throws MessagingException { 71 return new ExchangeStore(uri, context, callbacks); 72 } 73 74 /** 75 * eas://user:password@server/domain 76 * 77 * @param _uri 78 * @param application 79 * @throws MessagingException 80 */ 81 private ExchangeStore(String _uri, Context context, PersistentDataCallbacks callbacks) 82 throws MessagingException { 83 mContext = context; 84 try { 85 mUri = new URI(_uri); 86 } catch (URISyntaxException e) { 87 throw new MessagingException("Invalid uri for ExchangeStore"); 88 } 89 mCallbacks = callbacks; 90 91 String scheme = mUri.getScheme(); 92 int connectionSecurity; 93 if (scheme.equals("eas")) { 94 connectionSecurity = ExchangeTransport.CONNECTION_SECURITY_NONE; 95 } else if (scheme.equals("eas+ssl+")) { 96 connectionSecurity = ExchangeTransport.CONNECTION_SECURITY_SSL_REQUIRED; 97 } else { 98 throw new MessagingException("Unsupported protocol"); 99 } 100 101 mTransport = ExchangeTransport.getInstance(mUri, context); 102 } 103 104 /** 105 * Retrieve the underlying transport. Used primarily for testing. 106 * @return 107 */ 108 /* package */ ExchangeTransport getTransport() { 109 return mTransport; 110 } 111 112 @Override 113 public void checkSettings() throws MessagingException { 114 mTransport.checkSettings(mUri); 115 } 116 117 static public void addSystemAccount(Context context, Account acct) { 118 // This code was taken from sample code in AccountsTester 119 Bundle options = new Bundle(); 120 options.putString(EasAuthenticatorService.OPTIONS_USERNAME, acct.mEmailAddress); 121 options.putString(EasAuthenticatorService.OPTIONS_PASSWORD, acct.mHostAuthRecv.mPassword); 122 AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() { 123 public void run(AccountManagerFuture<Bundle> future) { 124 try { 125 Bundle bundle = future.getResult(); 126 bundle.keySet(); 127 Log.d(LOG_TAG, "account added: " + bundle); 128 } catch (OperationCanceledException e) { 129 Log.d(LOG_TAG, "addAccount was canceled"); 130 } catch (IOException e) { 131 Log.d(LOG_TAG, "addAccount failed: " + e); 132 } catch (AuthenticatorException e) { 133 Log.d(LOG_TAG, "addAccount failed: " + e); 134 } 135 136 } 137 }; 138 // Here's where we tell AccountManager about the new account. The addAccount 139 // method in AccountManager calls the addAccount method in our authenticator 140 // service (EasAuthenticatorService) 141 AccountManager.get(context).addAccount(Eas.ACCOUNT_MANAGER_TYPE, null, null, 142 options, null, callback, null); 143 } 144 145 @Override 146 public Folder getFolder(String name) throws MessagingException { 147 synchronized (mFolders) { 148 Folder folder = mFolders.get(name); 149 if (folder == null) { 150 folder = new ExchangeFolder(this, name); 151 mFolders.put(folder.getName(), folder); 152 } 153 return folder; 154 } 155 } 156 157 @Override 158 public Folder[] getPersonalNamespaces() throws MessagingException { 159 return new Folder[] { 160 getFolder(ExchangeTransport.FOLDER_INBOX), 161 }; 162 } 163 164 /** 165 * Get class of SettingActivity for this Store class. 166 * @return Activity class that has class method actionEditIncomingSettings() 167 */ 168 @Override 169 public Class<? extends android.app.Activity> getSettingActivityClass() { 170 return com.android.email.activity.setup.AccountSetupExchange.class; 171 } 172 173 /** 174 * Get class of sync'er for this Store class. Because exchange Sync rules are so different 175 * than IMAP or POP3, it's likely that an Exchange implementation will need its own sync 176 * controller. If so, this function must return a non-null value. 177 * 178 * @return Message Sync controller, or null to use default 179 */ 180 @Override 181 public StoreSynchronizer getMessageSynchronizer() { 182 return null; 183 } 184 185 /** 186 * Inform MessagingController that this store requires message structures to be prefetched 187 * before it can fetch message bodies (this is due to EAS protocol restrictions.) 188 * @return always true for EAS 189 */ 190 @Override 191 public boolean requireStructurePrefetch() { 192 return true; 193 } 194 195 /** 196 * Inform MessagingController that messages sent via EAS will be placed in the Sent folder 197 * automatically (server-side) and don't need to be uploaded. 198 * @return always false for EAS (assuming server-side copy is supported) 199 */ 200 @Override 201 public boolean requireCopyMessageToSentFolder() { 202 return false; 203 } 204 205 public static class ExchangeTransport { 206 public static final int CONNECTION_SECURITY_NONE = 0; 207 public static final int CONNECTION_SECURITY_SSL_REQUIRED = 1; 208 209 public static final String FOLDER_INBOX = Email.INBOX; 210 211 private static final String TAG = "ExchangeTransport"; 212 private final Context mContext; 213 214 private String mHost; 215 private String mDomain; 216 private String mUsername; 217 private String mPassword; 218 219 private static HashMap<String, ExchangeTransport> sUriToInstanceMap = 220 new HashMap<String, ExchangeTransport>(); 221 private static final HashMap<String, Integer> sFolderMap = new HashMap<String, Integer>(); 222 223 /** 224 * Public factory. The transport should be a singleton (per Uri) 225 */ 226 public synchronized static ExchangeTransport getInstance(URI uri, Context context) 227 throws MessagingException { 228 if (!uri.getScheme().equals("eas") && !uri.getScheme().equals("eas+ssl+")) { 229 throw new MessagingException("Invalid scheme"); 230 } 231 232 final String key = uri.toString(); 233 ExchangeTransport transport = sUriToInstanceMap.get(key); 234 if (transport == null) { 235 transport = new ExchangeTransport(uri, context); 236 sUriToInstanceMap.put(key, transport); 237 } 238 return transport; 239 } 240 241 /** 242 * Private constructor - use public factory. 243 */ 244 private ExchangeTransport(URI uri, Context context) throws MessagingException { 245 mContext = context; 246 setUri(uri); 247 } 248 249 /** 250 * Use the Uri to set up a newly-constructed transport 251 * @param uri 252 * @throws MessagingException 253 */ 254 private void setUri(final URI uri) throws MessagingException { 255 mHost = uri.getHost(); 256 if (mHost == null) { 257 throw new MessagingException("host not specified"); 258 } 259 260 mDomain = uri.getPath(); 261 if (!TextUtils.isEmpty(mDomain)) { 262 mDomain = mDomain.substring(1); 263 } 264 265 final String userInfo = uri.getUserInfo(); 266 if (userInfo == null) { 267 throw new MessagingException("user information not specifed"); 268 } 269 final String[] uinfo = userInfo.split(":", 2); 270 if (uinfo.length != 2) { 271 throw new MessagingException("user name and password not specified"); 272 } 273 mUsername = uinfo[0]; 274 mPassword = uinfo[1]; 275 } 276 277 /** 278 * Here's where we check the settings for EAS. 279 * @param uri the URI of the account to create 280 * @throws MessagingException if we can't authenticate the account 281 */ 282 public void checkSettings(URI uri) throws MessagingException { 283 setUri(uri); 284 boolean ssl = uri.getScheme().contains("ssl+"); 285 try { 286 int result = new EmailServiceProxy(mContext, SyncManager.class) 287 .validate("eas", mHost, mUsername, mPassword, ssl ? 443 : 80, ssl); 288 if (result != MessagingException.NO_ERROR) { 289 if (result == MessagingException.AUTHENTICATION_FAILED) { 290 throw new AuthenticationFailedException("Authentication failed."); 291 } else { 292 throw new MessagingException(result); 293 } 294 } 295 } catch (RemoteException e) { 296 throw new MessagingException("Call to validate generated an exception", e); 297 } 298 } 299 300 /** 301 * Typical helper function: Return existence of a given folder 302 */ 303 public boolean isFolderAvailable(final String folder) { 304 return sFolderMap.containsKey(folder); 305 } 306 } 307 308 public static class ExchangeFolder extends Folder { 309 310 private final ExchangeTransport mTransport; 311 @SuppressWarnings("unused") 312 private final ExchangeStore mStore; 313 @SuppressWarnings("unused") 314 private final String mName; 315 316 @SuppressWarnings("unused") 317 private PersistentDataCallbacks mPersistenceCallbacks; 318 319 public ExchangeFolder(ExchangeStore store, String name) 320 throws MessagingException { 321 mStore = store; 322 mTransport = store.getTransport(); 323 mName = name; 324 if (!mTransport.isFolderAvailable(name)) { 325 throw new MessagingException("folder not supported: " + name); 326 } 327 } 328 329 @Override 330 public void appendMessages(Message[] messages) throws MessagingException { 331 // TODO Implement this function 332 } 333 334 @Override 335 public void close(boolean expunge) throws MessagingException { 336 mPersistenceCallbacks = null; 337 // TODO Implement this function 338 } 339 340 @Override 341 public void copyMessages(Message[] msgs, Folder folder, MessageUpdateCallbacks callbacks) 342 throws MessagingException { 343 // TODO Implement this function 344 } 345 346 @Override 347 public boolean create(FolderType type) throws MessagingException { 348 // TODO Implement this function 349 return false; 350 } 351 352 @Override 353 public void delete(boolean recurse) throws MessagingException { 354 // TODO Implement this function 355 } 356 357 @Override 358 public boolean exists() throws MessagingException { 359 // TODO Implement this function 360 return false; 361 } 362 363 @Override 364 public Message[] expunge() throws MessagingException { 365 // TODO Implement this function 366 return null; 367 } 368 369 @Override 370 public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener) 371 throws MessagingException { 372 // TODO Implement this function 373 } 374 375 @Override 376 public Message getMessage(String uid) throws MessagingException { 377 // TODO Implement this function 378 return null; 379 } 380 381 @Override 382 public int getMessageCount() throws MessagingException { 383 // TODO Implement this function 384 return 0; 385 } 386 387 @Override 388 public Message[] getMessages(int start, int end, MessageRetrievalListener listener) 389 throws MessagingException { 390 // TODO Implement this function 391 return null; 392 } 393 394 @Override 395 public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException { 396 // TODO Implement this function 397 return null; 398 } 399 400 @Override 401 public Message[] getMessages(String[] uids, MessageRetrievalListener listener) 402 throws MessagingException { 403 // TODO Implement this function 404 return null; 405 } 406 407 @Override 408 public OpenMode getMode() throws MessagingException { 409 // TODO Implement this function 410 return null; 411 } 412 413 @Override 414 public String getName() { 415 // TODO Implement this function 416 return null; 417 } 418 419 @Override 420 public Flag[] getPermanentFlags() throws MessagingException { 421 // TODO Implement this function 422 return null; 423 } 424 425 @Override 426 public int getUnreadMessageCount() throws MessagingException { 427 // TODO Implement this function 428 return 0; 429 } 430 431 @Override 432 public boolean isOpen() { 433 // TODO Implement this function 434 return false; 435 } 436 437 @Override 438 public void open(OpenMode mode, PersistentDataCallbacks callbacks) 439 throws MessagingException { 440 mPersistenceCallbacks = callbacks; 441 // TODO Implement this function 442 } 443 444 @Override 445 public void setFlags(Message[] messages, Flag[] flags, boolean value) 446 throws MessagingException { 447 // TODO Implement this function 448 } 449 450 451 } 452 453 454} 455 456