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