Home | History | Annotate | Download | only in mail
      1 /*
      2  * Copyright (C) 2008 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;
     18 
     19 import com.android.email.Email;
     20 import com.android.email.R;
     21 
     22 import org.xmlpull.v1.XmlPullParserException;
     23 
     24 import android.content.Context;
     25 import android.content.res.XmlResourceParser;
     26 import android.os.Bundle;
     27 import android.util.Log;
     28 
     29 import java.io.IOException;
     30 
     31 /**
     32  * Store is the access point for an email message store. It's location can be
     33  * local or remote and no specific protocol is defined. Store is intended to
     34  * loosely model in combination the JavaMail classes javax.mail.Store and
     35  * javax.mail.Folder along with some additional functionality to improve
     36  * performance on mobile devices. Implementations of this class should focus on
     37  * making as few network connections as possible.
     38  */
     39 public abstract class Store {
     40 
     41     /**
     42      * String constants for known store schemes.
     43      */
     44     public static final String STORE_SCHEME_IMAP = "imap";
     45     public static final String STORE_SCHEME_POP3 = "pop3";
     46     public static final String STORE_SCHEME_EAS = "eas";
     47     public static final String STORE_SCHEME_LOCAL = "local";
     48 
     49     public static final String STORE_SECURITY_SSL = "+ssl";
     50     public static final String STORE_SECURITY_TLS = "+tls";
     51     public static final String STORE_SECURITY_TRUST_CERTIFICATES = "+trustallcerts";
     52 
     53     /**
     54      * A global suggestion to Store implementors on how much of the body
     55      * should be returned on FetchProfile.Item.BODY_SANE requests.
     56      */
     57     public static final int FETCH_BODY_SANE_SUGGESTED_SIZE = (50 * 1024);
     58 
     59     private static java.util.HashMap<String, Store> mStores =
     60         new java.util.HashMap<String, Store>();
     61 
     62     /**
     63      * Static named constructor.  It should be overrode by extending class.
     64      * Because this method will be called through reflection, it can not be protected.
     65      */
     66     public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks)
     67             throws MessagingException {
     68         throw new MessagingException("Store.newInstance: Unknown scheme in " + uri);
     69     }
     70 
     71     private static Store instantiateStore(String className, String uri, Context context,
     72             PersistentDataCallbacks callbacks)
     73         throws MessagingException {
     74         Object o = null;
     75         try {
     76             Class<?> c = Class.forName(className);
     77             // and invoke "newInstance" class method and instantiate store object.
     78             java.lang.reflect.Method m =
     79                 c.getMethod("newInstance", String.class, Context.class,
     80                         PersistentDataCallbacks.class);
     81             o = m.invoke(null, uri, context, callbacks);
     82         } catch (Exception e) {
     83             Log.d(Email.LOG_TAG, String.format(
     84                     "exception %s invoking %s.newInstance.(String, Context) method for %s",
     85                     e.toString(), className, uri));
     86             throw new MessagingException("can not instantiate Store object for " + uri);
     87         }
     88         if (!(o instanceof Store)) {
     89             throw new MessagingException(
     90                     uri + ": " + className + " create incompatible object");
     91         }
     92         return (Store) o;
     93     }
     94 
     95     /**
     96      * Look up descriptive information about a particular type of store.
     97      */
     98     public static class StoreInfo {
     99         public String mScheme;
    100         public String mClassName;
    101         public boolean mPushSupported = false;
    102         public int mVisibleLimitDefault;
    103         public int mVisibleLimitIncrement;
    104         public int mAccountInstanceLimit;
    105 
    106         // TODO cache result for performance - silly to keep reading the XML
    107         public static StoreInfo getStoreInfo(String scheme, Context context) {
    108             StoreInfo result = getStoreInfo(R.xml.stores_product, scheme, context);
    109             if (result == null) {
    110                 result = getStoreInfo(R.xml.stores, scheme, context);
    111             }
    112             return result;
    113         }
    114 
    115         public static StoreInfo getStoreInfo(int resourceId, String scheme, Context context) {
    116             try {
    117                 XmlResourceParser xml = context.getResources().getXml(resourceId);
    118                 int xmlEventType;
    119                 // walk through stores.xml file.
    120                 while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) {
    121                     if (xmlEventType == XmlResourceParser.START_TAG &&
    122                             "store".equals(xml.getName())) {
    123                         String xmlScheme = xml.getAttributeValue(null, "scheme");
    124                         if (scheme != null && scheme.startsWith(xmlScheme)) {
    125                             StoreInfo result = new StoreInfo();
    126                             result.mScheme = xmlScheme;
    127                             result.mClassName = xml.getAttributeValue(null, "class");
    128                             result.mPushSupported = xml.getAttributeBooleanValue(
    129                                     null, "push", false);
    130                             result.mVisibleLimitDefault = xml.getAttributeIntValue(
    131                                     null, "visibleLimitDefault", Email.VISIBLE_LIMIT_DEFAULT);
    132                             result.mVisibleLimitIncrement = xml.getAttributeIntValue(
    133                                     null, "visibleLimitIncrement", Email.VISIBLE_LIMIT_INCREMENT);
    134                             result.mAccountInstanceLimit = xml.getAttributeIntValue(
    135                                     null, "accountInstanceLimit", -1);
    136                             return result;
    137                         }
    138                     }
    139                 }
    140             } catch (XmlPullParserException e) {
    141                 // ignore
    142             } catch (IOException e) {
    143                 // ignore
    144             }
    145             return null;
    146         }
    147     }
    148 
    149     /**
    150      * Get an instance of a mail store. The URI is parsed as a standard URI and
    151      * the scheme is used to determine which protocol will be used.
    152      *
    153      * Although the URI format is somewhat protocol-specific, we use the following
    154      * guidelines wherever possible:
    155      *
    156      * scheme [+ security [+]] :// username : password @ host [ / resource ]
    157      *
    158      * Typical schemes include imap, pop3, local, eas.
    159      * Typical security models include SSL or TLS.
    160      * A + after the security identifier indicates "required".
    161      *
    162      * Username, password, and host are as expected.
    163      * Resource is protocol specific.  For example, IMAP uses it as the path prefix.  EAS uses it
    164      * as the domain.
    165      *
    166      * @param uri The URI of the store.
    167      * @return an initialized store of the appropriate class
    168      * @throws MessagingException
    169      */
    170     public synchronized static Store getInstance(String uri, Context context,
    171             PersistentDataCallbacks callbacks)
    172         throws MessagingException {
    173         Store store = mStores.get(uri);
    174         if (store == null) {
    175             StoreInfo info = StoreInfo.getStoreInfo(uri, context);
    176             if (info != null) {
    177                 store = instantiateStore(info.mClassName, uri, context, callbacks);
    178             }
    179 
    180             if (store != null) {
    181                 mStores.put(uri, store);
    182             }
    183         } else {
    184             // update the callbacks, which may have been null at creation time.
    185             store.setPersistentDataCallbacks(callbacks);
    186         }
    187 
    188         if (store == null) {
    189             throw new MessagingException("Unable to locate an applicable Store for " + uri);
    190         }
    191 
    192         return store;
    193     }
    194 
    195     /**
    196      * Delete an instance of a mail store.
    197      *
    198      * The store should have been notified already by calling delete(), and the caller should
    199      * also take responsibility for deleting the matching LocalStore, etc.
    200      * @param storeUri the store to be removed
    201      */
    202     public synchronized static void removeInstance(String storeUri) {
    203         mStores.remove(storeUri);
    204     }
    205 
    206     /**
    207      * Get class of SettingActivity for this Store class.
    208      * @return Activity class that has class method actionEditIncomingSettings().
    209      */
    210     public Class<? extends android.app.Activity> getSettingActivityClass() {
    211         // default SettingActivity class
    212         return com.android.email.activity.setup.AccountSetupIncoming.class;
    213     }
    214 
    215     /**
    216      * Get class of sync'er for this Store class
    217      * @return Message Sync controller, or null to use default
    218      */
    219     public StoreSynchronizer getMessageSynchronizer() {
    220         return null;
    221     }
    222 
    223     /**
    224      * Some stores cannot download a message based only on the uid, and need the message structure
    225      * to be preloaded and provided to them.  This method allows a remote store to signal this
    226      * requirement.  Most stores do not need this and do not need to overload this method, which
    227      * simply returns "false" in the base class.
    228      * @return Return true if the remote store requires structure prefetch
    229      */
    230     public boolean requireStructurePrefetch() {
    231         return false;
    232     }
    233 
    234     /**
    235      * Some protocols require that a sent message be copied (uploaded) into the Sent folder
    236      * while others can take care of it automatically (ideally, on the server).  This function
    237      * allows a given store to indicate which mode(s) it supports.
    238      * @return true if the store requires an upload into "sent", false if this happens automatically
    239      * for any sent message.
    240      */
    241     public boolean requireCopyMessageToSentFolder() {
    242         return true;
    243     }
    244 
    245     public abstract Folder getFolder(String name) throws MessagingException;
    246 
    247     public abstract Folder[] getPersonalNamespaces() throws MessagingException;
    248 
    249     public abstract void checkSettings() throws MessagingException;
    250 
    251     /**
    252      * Delete Store and its corresponding resources.
    253      * @throws MessagingException
    254      */
    255     public void delete() throws MessagingException {
    256     }
    257 
    258     /**
    259      * If a Store intends to implement callbacks, it should be prepared to update them
    260      * via overriding this method.  They may not be available at creation time (in which case they
    261      * will be passed in as null.
    262      * @param callbacks The updated provider of store callbacks
    263      */
    264     protected void setPersistentDataCallbacks(PersistentDataCallbacks callbacks) {
    265     }
    266 
    267     /**
    268      * Callback interface by which a Store can read and write persistent data.
    269      * TODO This needs to be made more generic & flexible
    270      */
    271     public interface PersistentDataCallbacks {
    272 
    273         /**
    274          * Provides a small place for Stores to store persistent data.
    275          * @param key identifier for the data (e.g. "sync.key" or "folder.id")
    276          * @param value The data to persist.  All data must be encoded into a string,
    277          * so use base64 or some other encoding if necessary.
    278          */
    279         public void setPersistentString(String key, String value);
    280 
    281         /**
    282          * @param key identifier for the data (e.g. "sync.key" or "folder.id")
    283          * @param defaultValue The data to return if no data was ever saved for this store
    284          * @return the data saved by the Store, or null if never set.
    285          */
    286         public String getPersistentString(String key, String defaultValue);
    287     }
    288 
    289     /**
    290      * Handle discovery of account settings using only the user's email address and password
    291      * @param context the context of the caller
    292      * @param emailAddress the email address of the exchange user
    293      * @param password the password of the exchange user
    294      * @return a Bundle containing an error code and a HostAuth (if successful)
    295      * @throws MessagingException
    296      */
    297     public Bundle autoDiscover(Context context, String emailAddress, String password)
    298             throws MessagingException {
    299         return null;
    300     }
    301 }
    302