ExchangeStore.java revision 17250429db16553b59d5df5e358f71406dd2b322
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.IEmailService; 32import com.android.exchange.IEmailServiceCallback; 33import com.android.exchange.SyncManager; 34import com.android.exchange.EmailContent.Attachment; 35 36import android.accounts.AccountManager; 37import android.accounts.AuthenticatorException; 38import android.accounts.Future2; 39import android.accounts.Future2Callback; 40import android.accounts.OperationCanceledException; 41import android.content.Context; 42import android.os.Bundle; 43import android.os.IBinder; 44import android.os.RemoteException; 45import android.text.TextUtils; 46import android.util.Config; 47import android.util.Log; 48 49import java.io.IOException; 50import java.net.URI; 51import java.net.URISyntaxException; 52import java.util.HashMap; 53 54/** 55 * This is a placeholder for use in Exchange implementations. It is based on the notion of 56 * lightweight adapter classes for Store, Folder, and Sender, and a common facade for the common 57 * Transport code. 58 */ 59public class ExchangeStore extends Store { 60 public static final String LOG_TAG = "ExchangeStore"; 61 62 private final Context mContext; 63 private URI mUri; 64 private PersistentDataCallbacks mCallbacks; 65 66 private final ExchangeTransport mTransport; 67 private final HashMap<String, Folder> mFolders = new HashMap<String, Folder>(); 68 69 private boolean mPushModeRunning = false; 70 71 /** 72 * Factory method. 73 */ 74 public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks) 75 throws MessagingException { 76 return new ExchangeStore(uri, context, callbacks); 77 } 78 79 /** 80 * eas://user:password@server/domain 81 * 82 * @param _uri 83 * @param application 84 * @throws MessagingException 85 */ 86 private ExchangeStore(String _uri, Context context, PersistentDataCallbacks callbacks) 87 throws MessagingException { 88 mContext = context; 89 try { 90 mUri = new URI(_uri); 91 } catch (URISyntaxException e) { 92 throw new MessagingException("Invalid uri for ExchangeStore"); 93 } 94 mCallbacks = callbacks; 95 96 String scheme = mUri.getScheme(); 97 int connectionSecurity; 98 if (scheme.equals("eas")) { 99 connectionSecurity = ExchangeTransport.CONNECTION_SECURITY_NONE; 100 } else if (scheme.equals("eas+ssl+")) { 101 connectionSecurity = ExchangeTransport.CONNECTION_SECURITY_SSL_REQUIRED; 102 } else { 103 throw new MessagingException("Unsupported protocol"); 104 } 105 106 mTransport = ExchangeTransport.getInstance(mUri, context); 107 } 108 109 /** 110 * Retrieve the underlying transport. Used primarily for testing. 111 * @return 112 */ 113 /* package */ ExchangeTransport getTransport() { 114 return mTransport; 115 } 116 117 @Override 118 public void checkSettings() throws MessagingException { 119 mTransport.checkSettings(mUri); 120 } 121 122 @Override 123 public Folder getFolder(String name) throws MessagingException { 124 synchronized (mFolders) { 125 Folder folder = mFolders.get(name); 126 if (folder == null) { 127 folder = new ExchangeFolder(this, name); 128 mFolders.put(folder.getName(), folder); 129 } 130 return folder; 131 } 132 } 133 134 @Override 135 public Folder[] getPersonalNamespaces() throws MessagingException { 136 return new Folder[] { 137 getFolder(ExchangeTransport.FOLDER_INBOX), 138 }; 139 } 140 141 /** 142 * For a store that supports push mode, this is the API that enables it or disables it. 143 * The store should use this API to start or stop its persistent connection service or thread. 144 * 145 * <p>Note, may be called multiple times, even after push mode has been started or stopped. 146 * 147 * @param enablePushMode start or stop push mode delivery 148 */ 149 @Override 150 public void enablePushModeDelivery(boolean enablePushMode) { 151 if (Config.LOGD && Email.DEBUG) { 152 if (enablePushMode && !mPushModeRunning) { 153 Log.d(Email.LOG_TAG, "start push mode"); 154 } else if (!enablePushMode && mPushModeRunning) { 155 Log.d(Email.LOG_TAG, "stop push mode"); 156 } else { 157 Log.d(Email.LOG_TAG, enablePushMode ? 158 "push mode already started" : "push mode already stopped"); 159 } 160 } 161 mPushModeRunning = enablePushMode; 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 * Blocking call that checks for a useable server connection, credentials, etc. 279 * @param uri the server/account to try and connect to 280 * @throws MessagingException thrown if the connection, server, account are not useable 281 */ 282 283 IEmailServiceCallback mCallback = new IEmailServiceCallback () { 284 285 public void status(int statusCode, int progress) throws RemoteException { 286 Log.d("Status: ", "Code = " + statusCode + ", progress = " + progress); 287 } 288 289 public IBinder asBinder() { return null; } 290 }; 291 292 /** 293 * Here's where we check the settings for EAS. 294 * @param uri the URI of the account to create 295 * @throws MessagingException if we can't authenticate the account 296 */ 297 public void checkSettings(URI uri) throws MessagingException { 298 setUri(uri); 299 boolean ssl = uri.getScheme().contains("ssl+"); 300 try { 301 IEmailService svc = EmailServiceProxy.getService(mContext, SyncManager.class); 302 int result = svc.validate("eas", mHost, mUsername, mPassword, ssl ? 443 : 80, ssl); 303 if (result != MessagingException.NO_ERROR) { 304 if (result == MessagingException.AUTHENTICATION_FAILED) { 305 throw new AuthenticationFailedException("Authentication failed."); 306 } else { 307 throw new MessagingException(result); 308 } 309 } else { 310 // This code was taken from sample code in AccountsTester 311 Bundle options = new Bundle(); 312 options.putString("username", mUsername); 313 options.putString("password", mPassword); 314 Future2Callback callback = new Future2Callback() { 315 public void run(Future2 future) { 316 try { 317 Bundle bundle = future.getResult(); 318 bundle.keySet(); 319 Log.d(TAG, "account added: " + bundle); 320 } catch (OperationCanceledException e) { 321 Log.d(TAG, "addAccount was canceled"); 322 } catch (IOException e) { 323 Log.d(TAG, "addAccount failed: " + e); 324 } catch (AuthenticatorException e) { 325 Log.d(TAG, "addAccount failed: " + e); 326 } 327 328 } 329 }; 330 // Here's where we tell AccountManager about the new account. The addAccount 331 // method in AccountManager calls the addAccount method in our authenticator 332 // service (EasAuthenticatorService) 333 AccountManager.get(mContext).addAccount(Eas.ACCOUNT_MANAGER_TYPE, null, null, 334 options, null, callback, null); 335 } 336 svc.loadAttachment(0, new Attachment(), mCallback); 337 } catch (RemoteException e) { 338 throw new MessagingException("Call to validate generated an exception", e); 339 } 340 } 341 342 /** 343 * Typical helper function: Return existence of a given folder 344 */ 345 public boolean isFolderAvailable(final String folder) { 346 return sFolderMap.containsKey(folder); 347 } 348 } 349 350 public static class ExchangeFolder extends Folder { 351 352 private final ExchangeTransport mTransport; 353 @SuppressWarnings("unused") 354 private final ExchangeStore mStore; 355 @SuppressWarnings("unused") 356 private final String mName; 357 358 @SuppressWarnings("unused") 359 private PersistentDataCallbacks mPersistenceCallbacks; 360 361 public ExchangeFolder(ExchangeStore store, String name) 362 throws MessagingException { 363 mStore = store; 364 mTransport = store.getTransport(); 365 mName = name; 366 if (!mTransport.isFolderAvailable(name)) { 367 throw new MessagingException("folder not supported: " + name); 368 } 369 } 370 371 @Override 372 public void appendMessages(Message[] messages) throws MessagingException { 373 // TODO Implement this function 374 } 375 376 @Override 377 public void close(boolean expunge) throws MessagingException { 378 mPersistenceCallbacks = null; 379 // TODO Implement this function 380 } 381 382 @Override 383 public void copyMessages(Message[] msgs, Folder folder, MessageUpdateCallbacks callbacks) 384 throws MessagingException { 385 // TODO Implement this function 386 } 387 388 @Override 389 public boolean create(FolderType type) throws MessagingException { 390 // TODO Implement this function 391 return false; 392 } 393 394 @Override 395 public void delete(boolean recurse) throws MessagingException { 396 // TODO Implement this function 397 } 398 399 @Override 400 public boolean exists() throws MessagingException { 401 // TODO Implement this function 402 return false; 403 } 404 405 @Override 406 public Message[] expunge() throws MessagingException { 407 // TODO Implement this function 408 return null; 409 } 410 411 @Override 412 public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener) 413 throws MessagingException { 414 // TODO Implement this function 415 } 416 417 @Override 418 public Message getMessage(String uid) throws MessagingException { 419 // TODO Implement this function 420 return null; 421 } 422 423 @Override 424 public int getMessageCount() throws MessagingException { 425 // TODO Implement this function 426 return 0; 427 } 428 429 @Override 430 public Message[] getMessages(int start, int end, MessageRetrievalListener listener) 431 throws MessagingException { 432 // TODO Implement this function 433 return null; 434 } 435 436 @Override 437 public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException { 438 // TODO Implement this function 439 return null; 440 } 441 442 @Override 443 public Message[] getMessages(String[] uids, MessageRetrievalListener listener) 444 throws MessagingException { 445 // TODO Implement this function 446 return null; 447 } 448 449 @Override 450 public OpenMode getMode() throws MessagingException { 451 // TODO Implement this function 452 return null; 453 } 454 455 @Override 456 public String getName() { 457 // TODO Implement this function 458 return null; 459 } 460 461 @Override 462 public Flag[] getPermanentFlags() throws MessagingException { 463 // TODO Implement this function 464 return null; 465 } 466 467 @Override 468 public int getUnreadMessageCount() throws MessagingException { 469 // TODO Implement this function 470 return 0; 471 } 472 473 @Override 474 public boolean isOpen() { 475 // TODO Implement this function 476 return false; 477 } 478 479 @Override 480 public void open(OpenMode mode, PersistentDataCallbacks callbacks) 481 throws MessagingException { 482 mPersistenceCallbacks = callbacks; 483 // TODO Implement this function 484 } 485 486 @Override 487 public void setFlags(Message[] messages, Flag[] flags, boolean value) 488 throws MessagingException { 489 // TODO Implement this function 490 } 491 492 493 } 494 495 496} 497 498