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