diff options
Diffstat (limited to 'provider_src/com/android/email/activity/setup/AccountSettingsUtils.java')
-rw-r--r-- | provider_src/com/android/email/activity/setup/AccountSettingsUtils.java | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/provider_src/com/android/email/activity/setup/AccountSettingsUtils.java b/provider_src/com/android/email/activity/setup/AccountSettingsUtils.java new file mode 100644 index 000000000..dbbd51ee7 --- /dev/null +++ b/provider_src/com/android/email/activity/setup/AccountSettingsUtils.java @@ -0,0 +1,433 @@ +/* + * 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); + } + +} |