• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.util.Log;
27 
28 import java.io.IOException;
29 
30 /**
31  * Store is the access point for an email message store. It's location can be
32  * local or remote and no specific protocol is defined. Store is intended to
33  * loosely model in combination the JavaMail classes javax.mail.Store and
34  * javax.mail.Folder along with some additional functionality to improve
35  * performance on mobile devices. Implementations of this class should focus on
36  * making as few network connections as possible.
37  */
38 public abstract class Store {
39 
40     /**
41      * String constants for known store schemes.
42      */
43     public static final String STORE_SCHEME_IMAP = "imap";
44     public static final String STORE_SCHEME_POP3 = "pop3";
45     public static final String STORE_SCHEME_EAS = "eas";
46     public static final String STORE_SCHEME_LOCAL = "local";
47 
48     public static final String STORE_SECURITY_SSL = "+ssl";
49     public static final String STORE_SECURITY_TLS = "+tls";
50     public static final String STORE_SECURITY_TRUST_CERTIFICATES = "+trustallcerts";
51 
52     /**
53      * A global suggestion to Store implementors on how much of the body
54      * should be returned on FetchProfile.Item.BODY_SANE requests.
55      */
56     public static final int FETCH_BODY_SANE_SUGGESTED_SIZE = (50 * 1024);
57 
58     private static java.util.HashMap<String, Store> mStores =
59         new java.util.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      */
newInstance(String uri, Context context, PersistentDataCallbacks callbacks)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 
instantiateStore(String className, String uri, Context context, PersistentDataCallbacks callbacks)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
getStoreInfo(String scheme, Context context)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 
getStoreInfo(int resourceId, String scheme, Context context)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      */
getInstance(String uri, Context context, PersistentDataCallbacks callbacks)169     public synchronized static Store getInstance(String uri, Context context,
170             PersistentDataCallbacks callbacks)
171         throws MessagingException {
172         Store store = mStores.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                 mStores.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      */
removeInstance(String storeUri)201     public synchronized static void removeInstance(String storeUri) {
202         mStores.remove(storeUri);
203     }
204 
205     /**
206      * Get class of SettingActivity for this Store class.
207      * @return Activity class that has class method actionEditIncomingSettings().
208      */
getSettingActivityClass()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      */
getMessageSynchronizer()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      */
requireStructurePrefetch()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      */
requireCopyMessageToSentFolder()240     public boolean requireCopyMessageToSentFolder() {
241         return true;
242     }
243 
getFolder(String name)244     public abstract Folder getFolder(String name) throws MessagingException;
245 
getPersonalNamespaces()246     public abstract Folder[] getPersonalNamespaces() throws MessagingException;
247 
checkSettings()248     public abstract void checkSettings() throws MessagingException;
249 
250     /**
251      * Delete Store and its corresponding resources.
252      * @throws MessagingException
253      */
delete()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      */
setPersistentDataCallbacks(PersistentDataCallbacks callbacks)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          */
setPersistentString(String key, String value)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          */
getPersistentString(String key, String defaultValue)285         public String getPersistentString(String key, String defaultValue);
286     }
287 }
288