| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.email.activity.setup; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.res.XmlResourceParser; |
| import android.net.Uri; |
| import android.text.TextUtils; |
| |
| import com.android.email.R; |
| import com.android.email.provider.AccountBackupRestore; |
| import com.android.emailcommon.Logging; |
| import com.android.emailcommon.VendorPolicyLoader; |
| import com.android.emailcommon.VendorPolicyLoader.OAuthProvider; |
| import com.android.emailcommon.VendorPolicyLoader.Provider; |
| import com.android.emailcommon.provider.Account; |
| import com.android.emailcommon.provider.EmailContent.AccountColumns; |
| import com.android.emailcommon.provider.QuickResponse; |
| import com.android.emailcommon.service.PolicyServiceProxy; |
| import com.android.emailcommon.utility.Utility; |
| import com.android.mail.utils.LogUtils; |
| import com.google.common.annotations.VisibleForTesting; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class AccountSettingsUtils { |
| |
| /** Pattern to match any part of a domain */ |
| private final static String WILD_STRING = "*"; |
| /** Will match any, single character */ |
| private final static char WILD_CHARACTER = '?'; |
| private final static String DOMAIN_SEPARATOR = "\\."; |
| |
| /** |
| * Commits the UI-related settings of an account to the provider. This is static so that it |
| * can be used by the various account activities. If the account has never been saved, this |
| * method saves it; otherwise, it just saves the settings. |
| * @param context the context of the caller |
| * @param account the account whose settings will be committed |
| */ |
| public static void commitSettings(Context context, Account account) { |
| if (!account.isSaved()) { |
| account.save(context); |
| |
| if (account.mPolicy != null) { |
| // TODO: we need better handling for unsupported policies |
| // For now, just clear the unsupported policies, as the server will (hopefully) |
| // just reject our sync attempts if it's not happy with half-measures |
| if (account.mPolicy.mProtocolPoliciesUnsupported != null) { |
| LogUtils.d(LogUtils.TAG, "Clearing unsupported policies " |
| + account.mPolicy.mProtocolPoliciesUnsupported); |
| account.mPolicy.mProtocolPoliciesUnsupported = null; |
| } |
| PolicyServiceProxy.setAccountPolicy2(context, |
| account.getId(), |
| account.mPolicy, |
| account.mSecuritySyncKey == null ? "" : account.mSecuritySyncKey, |
| false /* notify */); |
| } |
| |
| // Set up default quick responses here... |
| String[] defaultQuickResponses = |
| context.getResources().getStringArray(R.array.default_quick_responses); |
| ContentValues cv = new ContentValues(); |
| cv.put(QuickResponse.ACCOUNT_KEY, account.mId); |
| ContentResolver resolver = context.getContentResolver(); |
| for (String quickResponse: defaultQuickResponses) { |
| // Allow empty entries (some localizations may not want to have the maximum |
| // number) |
| if (!TextUtils.isEmpty(quickResponse)) { |
| cv.put(QuickResponse.TEXT, quickResponse); |
| resolver.insert(QuickResponse.CONTENT_URI, cv); |
| } |
| } |
| } else { |
| ContentValues cv = getAccountContentValues(account); |
| account.update(context, cv); |
| } |
| |
| // Update the backup (side copy) of the accounts |
| AccountBackupRestore.backup(context); |
| } |
| |
| /** |
| * Returns a set of content values to commit account changes (not including the foreign keys |
| * for the two host auth's and policy) to the database. Does not actually commit anything. |
| */ |
| public static ContentValues getAccountContentValues(Account account) { |
| ContentValues cv = new ContentValues(); |
| cv.put(AccountColumns.DISPLAY_NAME, account.getDisplayName()); |
| cv.put(AccountColumns.SENDER_NAME, account.getSenderName()); |
| cv.put(AccountColumns.SIGNATURE, account.getSignature()); |
| cv.put(AccountColumns.SYNC_INTERVAL, account.mSyncInterval); |
| cv.put(AccountColumns.FLAGS, account.mFlags); |
| cv.put(AccountColumns.SYNC_LOOKBACK, account.mSyncLookback); |
| cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey); |
| return cv; |
| } |
| |
| /** |
| * Create the request to get the authorization code. |
| * |
| * @param context |
| * @param provider The OAuth provider to register with |
| * @param emailAddress Email address to send as a hint to the oauth service. |
| * @return |
| */ |
| public static Uri createOAuthRegistrationRequest(final Context context, |
| final OAuthProvider provider, final String emailAddress) { |
| final Uri.Builder b = Uri.parse(provider.authEndpoint).buildUpon(); |
| b.appendQueryParameter("response_type", provider.responseType); |
| b.appendQueryParameter("client_id", provider.clientId); |
| b.appendQueryParameter("redirect_uri", provider.redirectUri); |
| b.appendQueryParameter("scope", provider.scope); |
| b.appendQueryParameter("state", provider.state); |
| b.appendQueryParameter("login_hint", emailAddress); |
| return b.build(); |
| } |
| |
| /** |
| * Search for a single resource containing known oauth provider definitions. |
| * |
| * @param context |
| * @param id String Id of the oauth provider. |
| * @return The OAuthProvider if found, null if not. |
| */ |
| public static OAuthProvider findOAuthProvider(final Context context, final String id) { |
| return findOAuthProvider(context, id, R.xml.oauth); |
| } |
| |
| public static List<OAuthProvider> getAllOAuthProviders(final Context context) { |
| try { |
| List<OAuthProvider> providers = new ArrayList<OAuthProvider>(); |
| final XmlResourceParser xml = context.getResources().getXml(R.xml.oauth); |
| int xmlEventType; |
| OAuthProvider provider = null; |
| while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { |
| if (xmlEventType == XmlResourceParser.START_TAG |
| && "provider".equals(xml.getName())) { |
| try { |
| provider = new OAuthProvider(); |
| provider.id = getXmlAttribute(context, xml, "id"); |
| provider.label = getXmlAttribute(context, xml, "label"); |
| provider.authEndpoint = getXmlAttribute(context, xml, "auth_endpoint"); |
| provider.tokenEndpoint = getXmlAttribute(context, xml, "token_endpoint"); |
| provider.refreshEndpoint = getXmlAttribute(context, xml, |
| "refresh_endpoint"); |
| provider.responseType = getXmlAttribute(context, xml, "response_type"); |
| provider.redirectUri = getXmlAttribute(context, xml, "redirect_uri"); |
| provider.scope = getXmlAttribute(context, xml, "scope"); |
| provider.state = getXmlAttribute(context, xml, "state"); |
| provider.clientId = getXmlAttribute(context, xml, "client_id"); |
| provider.clientSecret = getXmlAttribute(context, xml, "client_secret"); |
| providers.add(provider); |
| } catch (IllegalArgumentException e) { |
| LogUtils.w(Logging.LOG_TAG, "providers line: " + xml.getLineNumber() + |
| "; Domain contains multiple globals"); |
| } |
| } |
| } |
| return providers; |
| } catch (Exception e) { |
| LogUtils.e(Logging.LOG_TAG, "Error while trying to load provider settings.", e); |
| } |
| return null; |
| } |
| |
| /** |
| * Search for a single resource containing known oauth provider definitions. |
| * |
| * @param context |
| * @param id String Id of the oauth provider. |
| * @param resourceId ResourceId of the xml file to search. |
| * @return The OAuthProvider if found, null if not. |
| */ |
| public static OAuthProvider findOAuthProvider(final Context context, final String id, |
| final int resourceId) { |
| // TODO: Consider adding a way to cache this file during new account setup, so that we |
| // don't need to keep loading the file over and over. |
| // TODO: need a mechanism to get a list of all supported OAuth providers so that we can |
| // offer the user a choice of who to authenticate with. |
| try { |
| final XmlResourceParser xml = context.getResources().getXml(resourceId); |
| int xmlEventType; |
| OAuthProvider provider = null; |
| while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { |
| if (xmlEventType == XmlResourceParser.START_TAG |
| && "provider".equals(xml.getName())) { |
| String providerId = getXmlAttribute(context, xml, "id"); |
| try { |
| if (TextUtils.equals(id, providerId)) { |
| provider = new OAuthProvider(); |
| provider.id = id; |
| provider.label = getXmlAttribute(context, xml, "label"); |
| provider.authEndpoint = getXmlAttribute(context, xml, "auth_endpoint"); |
| provider.tokenEndpoint = getXmlAttribute(context, xml, "token_endpoint"); |
| provider.refreshEndpoint = getXmlAttribute(context, xml, |
| "refresh_endpoint"); |
| provider.responseType = getXmlAttribute(context, xml, "response_type"); |
| provider.redirectUri = getXmlAttribute(context, xml, "redirect_uri"); |
| provider.scope = getXmlAttribute(context, xml, "scope"); |
| provider.state = getXmlAttribute(context, xml, "state"); |
| provider.clientId = getXmlAttribute(context, xml, "client_id"); |
| provider.clientSecret = getXmlAttribute(context, xml, "client_secret"); |
| return provider; |
| } |
| } catch (IllegalArgumentException e) { |
| LogUtils.w(Logging.LOG_TAG, "providers line: " + xml.getLineNumber() + |
| "; Domain contains multiple globals"); |
| } |
| } |
| } |
| } catch (Exception e) { |
| LogUtils.e(Logging.LOG_TAG, "Error while trying to load provider settings.", e); |
| } |
| return null; |
| } |
| |
| /** |
| * Search the list of known Email providers looking for one that matches the user's email |
| * domain. We check for vendor supplied values first, then we look in providers_product.xml, |
| * and finally by the entries in platform providers.xml. This provides a nominal override |
| * capability. |
| * |
| * A match is defined as any provider entry for which the "domain" attribute matches. |
| * |
| * @param domain The domain portion of the user's email address |
| * @return suitable Provider definition, or null if no match found |
| */ |
| public static Provider findProviderForDomain(Context context, String domain) { |
| Provider p = VendorPolicyLoader.getInstance(context).findProviderForDomain(domain); |
| if (p == null) { |
| p = findProviderForDomain(context, domain, R.xml.providers_product); |
| } |
| if (p == null) { |
| p = findProviderForDomain(context, domain, R.xml.providers); |
| } |
| return p; |
| } |
| |
| /** |
| * Search a single resource containing known Email provider definitions. |
| * |
| * @param domain The domain portion of the user's email address |
| * @param resourceId Id of the provider resource to scan |
| * @return suitable Provider definition, or null if no match found |
| */ |
| /*package*/ static Provider findProviderForDomain( |
| Context context, String domain, int resourceId) { |
| try { |
| XmlResourceParser xml = context.getResources().getXml(resourceId); |
| int xmlEventType; |
| Provider provider = null; |
| while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { |
| if (xmlEventType == XmlResourceParser.START_TAG |
| && "provider".equals(xml.getName())) { |
| String providerDomain = getXmlAttribute(context, xml, "domain"); |
| try { |
| if (matchProvider(domain, providerDomain)) { |
| provider = new Provider(); |
| provider.id = getXmlAttribute(context, xml, "id"); |
| provider.label = getXmlAttribute(context, xml, "label"); |
| provider.domain = domain.toLowerCase(); |
| provider.note = getXmlAttribute(context, xml, "note"); |
| // TODO: Maybe this should actually do a lookup of the OAuth provider |
| // here, and keep a pointer to it rather than a textual key. |
| // To do this probably requires caching oauth.xml, otherwise the lookup |
| // is expensive and likely to happen repeatedly. |
| provider.oauth = getXmlAttribute(context, xml, "oauth"); |
| } |
| } catch (IllegalArgumentException e) { |
| LogUtils.w(Logging.LOG_TAG, "providers line: " + xml.getLineNumber() + |
| "; Domain contains multiple globals"); |
| } |
| } |
| else if (xmlEventType == XmlResourceParser.START_TAG |
| && "incoming".equals(xml.getName()) |
| && provider != null) { |
| provider.incomingUriTemplate = getXmlAttribute(context, xml, "uri"); |
| provider.incomingUsernameTemplate = getXmlAttribute(context, xml, "username"); |
| } |
| else if (xmlEventType == XmlResourceParser.START_TAG |
| && "outgoing".equals(xml.getName()) |
| && provider != null) { |
| provider.outgoingUriTemplate = getXmlAttribute(context, xml, "uri"); |
| provider.outgoingUsernameTemplate = getXmlAttribute(context, xml, "username"); |
| } |
| else if (xmlEventType == XmlResourceParser.START_TAG |
| && "incoming-fallback".equals(xml.getName()) |
| && provider != null) { |
| provider.altIncomingUriTemplate = getXmlAttribute(context, xml, "uri"); |
| provider.altIncomingUsernameTemplate = |
| getXmlAttribute(context, xml, "username"); |
| } |
| else if (xmlEventType == XmlResourceParser.START_TAG |
| && "outgoing-fallback".equals(xml.getName()) |
| && provider != null) { |
| provider.altOutgoingUriTemplate = getXmlAttribute(context, xml, "uri"); |
| provider.altOutgoingUsernameTemplate = |
| getXmlAttribute(context, xml, "username"); |
| } |
| else if (xmlEventType == XmlResourceParser.END_TAG |
| && "provider".equals(xml.getName()) |
| && provider != null) { |
| return provider; |
| } |
| } |
| } |
| catch (Exception e) { |
| LogUtils.e(Logging.LOG_TAG, "Error while trying to load provider settings.", e); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns true if the string <code>s1</code> matches the string <code>s2</code>. The string |
| * <code>s2</code> may contain any number of wildcards -- a '?' character -- and/or asterisk |
| * characters -- '*'. Wildcards match any single character, while the asterisk matches a domain |
| * part (i.e. substring demarcated by a period, '.') |
| */ |
| @VisibleForTesting |
| public static boolean matchProvider(String testDomain, String providerDomain) { |
| String[] testParts = testDomain.split(DOMAIN_SEPARATOR); |
| String[] providerParts = providerDomain.split(DOMAIN_SEPARATOR); |
| if (testParts.length != providerParts.length) { |
| return false; |
| } |
| for (int i = 0; i < testParts.length; i++) { |
| String testPart = testParts[i].toLowerCase(); |
| String providerPart = providerParts[i].toLowerCase(); |
| if (!providerPart.equals(WILD_STRING) && |
| !matchWithWildcards(testPart, providerPart)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean matchWithWildcards(String testPart, String providerPart) { |
| int providerLength = providerPart.length(); |
| if (testPart.length() != providerLength){ |
| return false; |
| } |
| for (int i = 0; i < providerLength; i++) { |
| char testChar = testPart.charAt(i); |
| char providerChar = providerPart.charAt(i); |
| if (testChar != providerChar && providerChar != WILD_CHARACTER) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Attempts to get the given attribute as a String resource first, and if it fails |
| * returns the attribute as a simple String value. |
| * @param xml |
| * @param name |
| * @return the requested resource |
| */ |
| private static String getXmlAttribute(Context context, XmlResourceParser xml, String name) { |
| int resId = xml.getAttributeResourceValue(null, name, 0); |
| if (resId == 0) { |
| return xml.getAttributeValue(null, name); |
| } |
| else { |
| return context.getString(resId); |
| } |
| } |
| |
| /** |
| * Infer potential email server addresses from domain names |
| * |
| * Incoming: Prepend "imap" or "pop3" to domain, unless "pop", "pop3", |
| * "imap", or "mail" are found. |
| * Outgoing: Prepend "smtp" if domain starts with any in the host prefix array |
| * |
| * @param server name as we know it so far |
| * @param incoming "pop3" or "imap" (or null) |
| * @param outgoing "smtp" or null |
| * @return the post-processed name for use in the UI |
| */ |
| public static String inferServerName(Context context, String server, String incoming, |
| String outgoing) { |
| // Default values cause entire string to be kept, with prepended server string |
| int keepFirstChar = 0; |
| int firstDotIndex = server.indexOf('.'); |
| if (firstDotIndex != -1) { |
| // look at first word and decide what to do |
| String firstWord = server.substring(0, firstDotIndex).toLowerCase(); |
| String[] hostPrefixes = |
| context.getResources().getStringArray(R.array.smtp_host_prefixes); |
| boolean canSubstituteSmtp = Utility.arrayContains(hostPrefixes, firstWord); |
| boolean isMail = "mail".equals(firstWord); |
| // Now decide what to do |
| if (incoming != null) { |
| // For incoming, we leave imap/pop/pop3/mail alone, or prepend incoming |
| if (canSubstituteSmtp || isMail) { |
| return server; |
| } |
| } else { |
| // For outgoing, replace imap/pop/pop3 with outgoing, leave mail alone, or |
| // prepend outgoing |
| if (canSubstituteSmtp) { |
| keepFirstChar = firstDotIndex + 1; |
| } else if (isMail) { |
| return server; |
| } else { |
| // prepend |
| } |
| } |
| } |
| return ((incoming != null) ? incoming : outgoing) + '.' + server.substring(keepFirstChar); |
| } |
| |
| } |