1/* 2 * Copyright (C) 2009 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 17package com.android.email.activity.setup; 18 19import android.content.ContentValues; 20import android.content.Context; 21import android.content.res.XmlResourceParser; 22import android.text.Editable; 23import android.util.Log; 24import android.widget.EditText; 25 26import com.android.email.R; 27import com.android.email.VendorPolicyLoader; 28import com.android.email.provider.AccountBackupRestore; 29import com.android.emailcommon.Logging; 30import com.android.emailcommon.provider.Account; 31import com.android.emailcommon.provider.EmailContent.AccountColumns; 32import com.google.common.annotations.VisibleForTesting; 33 34import java.io.Serializable; 35 36public class AccountSettingsUtils { 37 38 /** Pattern to match any part of a domain */ 39 private final static String WILD_STRING = "*"; 40 /** Will match any, single character */ 41 private final static char WILD_CHARACTER = '?'; 42 private final static String DOMAIN_SEPARATOR = "\\."; 43 44 /** 45 * Commits the UI-related settings of an account to the provider. This is static so that it 46 * can be used by the various account activities. If the account has never been saved, this 47 * method saves it; otherwise, it just saves the settings. 48 * @param context the context of the caller 49 * @param account the account whose settings will be committed 50 */ 51 public static void commitSettings(Context context, Account account) { 52 if (!account.isSaved()) { 53 account.save(context); 54 } else { 55 ContentValues cv = getAccountContentValues(account); 56 account.update(context, cv); 57 } 58 // Update the backup (side copy) of the accounts 59 AccountBackupRestore.backup(context); 60 } 61 62 /** 63 * Returns a set of content values to commit account changes (not including the foreign keys 64 * for the two host auth's and policy) to the database. Does not actually commit anything. 65 */ 66 public static ContentValues getAccountContentValues(Account account) { 67 ContentValues cv = new ContentValues(); 68 cv.put(AccountColumns.IS_DEFAULT, account.mIsDefault); 69 cv.put(AccountColumns.DISPLAY_NAME, account.getDisplayName()); 70 cv.put(AccountColumns.SENDER_NAME, account.getSenderName()); 71 cv.put(AccountColumns.SIGNATURE, account.getSignature()); 72 cv.put(AccountColumns.SYNC_INTERVAL, account.mSyncInterval); 73 cv.put(AccountColumns.RINGTONE_URI, account.mRingtoneUri); 74 cv.put(AccountColumns.FLAGS, account.mFlags); 75 cv.put(AccountColumns.SYNC_LOOKBACK, account.mSyncLookback); 76 cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey); 77 return cv; 78 } 79 80 /** 81 * Search the list of known Email providers looking for one that matches the user's email 82 * domain. We check for vendor supplied values first, then we look in providers_product.xml, 83 * and finally by the entries in platform providers.xml. This provides a nominal override 84 * capability. 85 * 86 * A match is defined as any provider entry for which the "domain" attribute matches. 87 * 88 * @param domain The domain portion of the user's email address 89 * @return suitable Provider definition, or null if no match found 90 */ 91 public static Provider findProviderForDomain(Context context, String domain) { 92 Provider p = VendorPolicyLoader.getInstance(context).findProviderForDomain(domain); 93 if (p == null) { 94 p = findProviderForDomain(context, domain, R.xml.providers_product); 95 } 96 if (p == null) { 97 p = findProviderForDomain(context, domain, R.xml.providers); 98 } 99 return p; 100 } 101 102 /** 103 * Search a single resource containing known Email provider definitions. 104 * 105 * @param domain The domain portion of the user's email address 106 * @param resourceId Id of the provider resource to scan 107 * @return suitable Provider definition, or null if no match found 108 */ 109 /*package*/ static Provider findProviderForDomain( 110 Context context, String domain, int resourceId) { 111 try { 112 XmlResourceParser xml = context.getResources().getXml(resourceId); 113 int xmlEventType; 114 Provider provider = null; 115 while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { 116 if (xmlEventType == XmlResourceParser.START_TAG 117 && "provider".equals(xml.getName())) { 118 String providerDomain = getXmlAttribute(context, xml, "domain"); 119 try { 120 if (matchProvider(domain, providerDomain)) { 121 provider = new Provider(); 122 provider.id = getXmlAttribute(context, xml, "id"); 123 provider.label = getXmlAttribute(context, xml, "label"); 124 provider.domain = domain.toLowerCase(); 125 provider.note = getXmlAttribute(context, xml, "note"); 126 } 127 } catch (IllegalArgumentException e) { 128 Log.w(Logging.LOG_TAG, "providers line: " + xml.getLineNumber() + 129 "; Domain contains multiple globals"); 130 } 131 } 132 else if (xmlEventType == XmlResourceParser.START_TAG 133 && "incoming".equals(xml.getName()) 134 && provider != null) { 135 provider.incomingUriTemplate = getXmlAttribute(context, xml, "uri"); 136 provider.incomingUsernameTemplate = getXmlAttribute(context, xml, "username"); 137 } 138 else if (xmlEventType == XmlResourceParser.START_TAG 139 && "outgoing".equals(xml.getName()) 140 && provider != null) { 141 provider.outgoingUriTemplate = getXmlAttribute(context, xml, "uri"); 142 provider.outgoingUsernameTemplate = getXmlAttribute(context, xml, "username"); 143 } 144 else if (xmlEventType == XmlResourceParser.END_TAG 145 && "provider".equals(xml.getName()) 146 && provider != null) { 147 return provider; 148 } 149 } 150 } 151 catch (Exception e) { 152 Log.e(Logging.LOG_TAG, "Error while trying to load provider settings.", e); 153 } 154 return null; 155 } 156 157 /** 158 * Returns true if the string <code>s1</code> matches the string <code>s2</code>. The string 159 * <code>s2</code> may contain any number of wildcards -- a '?' character -- and/or asterisk 160 * characters -- '*'. Wildcards match any single character, while the asterisk matches a domain 161 * part (i.e. substring demarcated by a period, '.') 162 */ 163 @VisibleForTesting 164 static boolean matchProvider(String testDomain, String providerDomain) { 165 String[] testParts = testDomain.split(DOMAIN_SEPARATOR); 166 String[] providerParts = providerDomain.split(DOMAIN_SEPARATOR); 167 if (testParts.length != providerParts.length) { 168 return false; 169 } 170 for (int i = 0; i < testParts.length; i++) { 171 String testPart = testParts[i].toLowerCase(); 172 String providerPart = providerParts[i].toLowerCase(); 173 if (!providerPart.equals(WILD_STRING) && 174 !matchWithWildcards(testPart, providerPart)) { 175 return false; 176 } 177 } 178 return true; 179 } 180 181 private static boolean matchWithWildcards(String testPart, String providerPart) { 182 int providerLength = providerPart.length(); 183 if (testPart.length() != providerLength){ 184 return false; 185 } 186 for (int i = 0; i < providerLength; i++) { 187 char testChar = testPart.charAt(i); 188 char providerChar = providerPart.charAt(i); 189 if (testChar != providerChar && providerChar != WILD_CHARACTER) { 190 return false; 191 } 192 } 193 return true; 194 } 195 196 /** 197 * Attempts to get the given attribute as a String resource first, and if it fails 198 * returns the attribute as a simple String value. 199 * @param xml 200 * @param name 201 * @return the requested resource 202 */ 203 private static String getXmlAttribute(Context context, XmlResourceParser xml, String name) { 204 int resId = xml.getAttributeResourceValue(null, name, 0); 205 if (resId == 0) { 206 return xml.getAttributeValue(null, name); 207 } 208 else { 209 return context.getString(resId); 210 } 211 } 212 213 public static class Provider implements Serializable { 214 private static final long serialVersionUID = 8511656164616538989L; 215 216 public String id; 217 public String label; 218 public String domain; 219 public String incomingUriTemplate; 220 public String incomingUsernameTemplate; 221 public String outgoingUriTemplate; 222 public String outgoingUsernameTemplate; 223 public String incomingUri; 224 public String incomingUsername; 225 public String outgoingUri; 226 public String outgoingUsername; 227 public String note; 228 229 /** 230 * Expands templates in all of the provider fields that support them. Currently, 231 * templates are used in 4 fields -- incoming and outgoing URI and user name. 232 * @param email user-specified data used to replace template values 233 */ 234 public void expandTemplates(String email) { 235 String[] emailParts = email.split("@"); 236 String user = emailParts[0]; 237 238 incomingUri = expandTemplate(incomingUriTemplate, email, user); 239 incomingUsername = expandTemplate(incomingUsernameTemplate, email, user); 240 outgoingUri = expandTemplate(outgoingUriTemplate, email, user); 241 outgoingUsername = expandTemplate(outgoingUsernameTemplate, email, user); 242 } 243 244 /** 245 * Replaces all parameterized values in the given template. The values replaced are 246 * $domain, $user and $email. 247 */ 248 private String expandTemplate(String template, String email, String user) { 249 String returnString = template; 250 returnString = returnString.replaceAll("\\$email", email); 251 returnString = returnString.replaceAll("\\$user", user); 252 returnString = returnString.replaceAll("\\$domain", domain); 253 return returnString; 254 } 255 } 256 257 /** 258 * Infer potential email server addresses from domain names 259 * 260 * Incoming: Prepend "imap" or "pop3" to domain, unless "pop", "pop3", 261 * "imap", or "mail" are found. 262 * Outgoing: Prepend "smtp" if "pop", "pop3", "imap" are found. 263 * Leave "mail" as-is. 264 * TBD: Are there any useful defaults for exchange? 265 * 266 * @param server name as we know it so far 267 * @param incoming "pop3" or "imap" (or null) 268 * @param outgoing "smtp" or null 269 * @return the post-processed name for use in the UI 270 */ 271 public static String inferServerName(String server, String incoming, String outgoing) { 272 // Default values cause entire string to be kept, with prepended server string 273 int keepFirstChar = 0; 274 int firstDotIndex = server.indexOf('.'); 275 if (firstDotIndex != -1) { 276 // look at first word and decide what to do 277 String firstWord = server.substring(0, firstDotIndex).toLowerCase(); 278 boolean isImapOrPop = "imap".equals(firstWord) 279 || "pop3".equals(firstWord) || "pop".equals(firstWord); 280 boolean isMail = "mail".equals(firstWord); 281 // Now decide what to do 282 if (incoming != null) { 283 // For incoming, we leave imap/pop/pop3/mail alone, or prepend incoming 284 if (isImapOrPop || isMail) { 285 return server; 286 } 287 } else { 288 // For outgoing, replace imap/pop/pop3 with outgoing, leave mail alone, or 289 // prepend outgoing 290 if (isImapOrPop) { 291 keepFirstChar = firstDotIndex + 1; 292 } else if (isMail) { 293 return server; 294 } else { 295 // prepend 296 } 297 } 298 } 299 return ((incoming != null) ? incoming : outgoing) + '.' + server.substring(keepFirstChar); 300 } 301 302 /** 303 * Helper to set error status on password fields that have leading or trailing spaces 304 */ 305 public static void checkPasswordSpaces(Context context, EditText passwordField) { 306 Editable password = passwordField.getText(); 307 int length = password.length(); 308 if (length > 0) { 309 if (password.charAt(0) == ' ' || password.charAt(length-1) == ' ') { 310 passwordField.setError(context.getString(R.string.account_password_spaces_error)); 311 } 312 } 313 } 314 315} 316