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