Home | History | Annotate | Download | only in setup
      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 
     17 package com.android.email.activity.setup;
     18 
     19 import android.content.ContentValues;
     20 import android.content.Context;
     21 import android.content.res.XmlResourceParser;
     22 import android.text.Editable;
     23 import android.util.Log;
     24 import android.widget.EditText;
     25 
     26 import com.android.email.R;
     27 import com.android.email.VendorPolicyLoader;
     28 import com.android.email.provider.AccountBackupRestore;
     29 import com.android.emailcommon.Logging;
     30 import com.android.emailcommon.provider.Account;
     31 import com.android.emailcommon.provider.EmailContent.AccountColumns;
     32 import com.google.common.annotations.VisibleForTesting;
     33 
     34 import java.io.Serializable;
     35 
     36 public 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