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