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