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