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