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