Home | History | Annotate | Download | only in emailcommon
      1 /*
      2  * Copyright (C) 2010 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.emailcommon;
     18 
     19 import android.content.Context;
     20 import android.content.pm.ApplicationInfo;
     21 import android.content.pm.PackageManager.NameNotFoundException;
     22 import android.os.Bundle;
     23 
     24 import com.android.mail.utils.LogUtils;
     25 
     26 import java.io.Serializable;
     27 import java.lang.reflect.Method;
     28 
     29 /**
     30  * A bridge class to the email vendor policy apk.
     31  *
     32  * <p>Email vendor policy is a system apk named "com.android.email.helper".  When exists, it must
     33  * contain a class called "com.android.email.policy.EmailPolicy" with a static public method
     34  * <code>Bundle getPolicy(String, Bundle)</code>, which serves vendor specific configurations.
     35  *
     36  * <p>A vendor policy apk is optional.  The email application will operate properly when none is
     37  * found.
     38  */
     39 public class VendorPolicyLoader {
     40     private static final String POLICY_PACKAGE = "com.android.email.policy";
     41     private static final String POLICY_CLASS = POLICY_PACKAGE + ".EmailPolicy";
     42     private static final String GET_POLICY_METHOD = "getPolicy";
     43     private static final Class<?>[] ARGS = new Class<?>[] {String.class, Bundle.class};
     44 
     45     // call keys and i/o bundle keys
     46     // when there is only one parameter or return value, use call key
     47     private static final String USE_ALTERNATE_EXCHANGE_STRINGS = "useAlternateExchangeStrings";
     48     private static final String GET_IMAP_ID = "getImapId";
     49     private static final String GET_IMAP_ID_USER = "getImapId.user";
     50     private static final String GET_IMAP_ID_HOST = "getImapId.host";
     51     private static final String GET_IMAP_ID_CAPA = "getImapId.capabilities";
     52     private static final String FIND_PROVIDER = "findProvider";
     53     private static final String FIND_PROVIDER_IN_URI = "findProvider.inUri";
     54     private static final String FIND_PROVIDER_IN_USER = "findProvider.inUser";
     55     private static final String FIND_PROVIDER_OUT_URI = "findProvider.outUri";
     56     private static final String FIND_PROVIDER_OUT_USER = "findProvider.outUser";
     57     private static final String FIND_PROVIDER_NOTE = "findProvider.note";
     58 
     59     /** Singleton instance */
     60     private static VendorPolicyLoader sInstance;
     61 
     62     private final Method mPolicyMethod;
     63 
     64     public static VendorPolicyLoader getInstance(Context context) {
     65         if (sInstance == null) {
     66             // It's okay to instantiate VendorPolicyLoader multiple times.  No need to synchronize.
     67             sInstance = new VendorPolicyLoader(context);
     68         }
     69         return sInstance;
     70     }
     71 
     72     /**
     73      * For testing only.
     74      *
     75      * Replaces the instance with a new instance that loads a specified class.
     76      */
     77     public static void injectPolicyForTest(Context context, String apkPackageName, Class<?> clazz) {
     78         String name = clazz.getName();
     79         LogUtils.d(Logging.LOG_TAG, String.format("Using policy: package=%s name=%s",
     80                 apkPackageName, name));
     81         sInstance = new VendorPolicyLoader(context, apkPackageName, name, true);
     82     }
     83 
     84     /**
     85      * For testing only.
     86      *
     87      * Clear the instance so that the next {@link #getInstance} call will return a regular,
     88      * non-injected instance.
     89      */
     90     public static void clearInstanceForTest() {
     91         sInstance = null;
     92     }
     93 
     94     private VendorPolicyLoader(Context context) {
     95         this(context, POLICY_PACKAGE, POLICY_CLASS, false);
     96     }
     97 
     98     /**
     99      * Constructor for testing, where we need to use an alternate package/class name, and skip
    100      * the system apk check.
    101      */
    102     /* package */ VendorPolicyLoader(Context context, String apkPackageName, String className,
    103             boolean allowNonSystemApk) {
    104         if (!allowNonSystemApk && !isSystemPackage(context, apkPackageName)) {
    105             mPolicyMethod = null;
    106             return;
    107         }
    108 
    109         Class<?> clazz = null;
    110         Method method = null;
    111         try {
    112             final Context policyContext = context.createPackageContext(apkPackageName,
    113                     Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
    114             final ClassLoader classLoader = policyContext.getClassLoader();
    115             clazz = classLoader.loadClass(className);
    116             method = clazz.getMethod(GET_POLICY_METHOD, ARGS);
    117         } catch (NameNotFoundException ignore) {
    118             // Package not found -- it's okay - there's no policy .apk found, which is OK
    119         } catch (ClassNotFoundException e) {
    120             // Class not found -- probably not OK, but let's not crash here
    121             LogUtils.w(Logging.LOG_TAG, "VendorPolicyLoader: " + e);
    122         } catch (NoSuchMethodException e) {
    123             // Method not found -- probably not OK, but let's not crash here
    124             LogUtils.w(Logging.LOG_TAG, "VendorPolicyLoader: " + e);
    125         }
    126         mPolicyMethod = method;
    127     }
    128 
    129     // Not private for testing
    130     /* package */ static boolean isSystemPackage(Context context, String packageName) {
    131         try {
    132             ApplicationInfo ai = context.getPackageManager().getApplicationInfo(packageName, 0);
    133             return (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    134         } catch (NameNotFoundException e) {
    135             return false; // Package not found.
    136         }
    137     }
    138 
    139     /**
    140      * Calls the getPolicy method in the policy apk, if one exists.  This method never returns null;
    141      * It returns an empty {@link Bundle} when there is no policy apk (or even if the inner
    142      * getPolicy returns null).
    143      */
    144     // Not private for testing
    145     /* package */ Bundle getPolicy(String policy, Bundle args) {
    146         Bundle ret = null;
    147         if (mPolicyMethod != null) {
    148             try {
    149                 ret = (Bundle) mPolicyMethod.invoke(null, policy, args);
    150             } catch (Exception e) {
    151                 LogUtils.w(Logging.LOG_TAG, "VendorPolicyLoader", e);
    152             }
    153         }
    154         return (ret != null) ? ret : Bundle.EMPTY;
    155     }
    156 
    157     /**
    158      * Returns true if alternate exchange descriptive text is required.
    159      *
    160      * Vendor function:
    161      *  Select: USE_ALTERNATE_EXCHANGE_STRINGS
    162      *  Params: none
    163      *  Result: USE_ALTERNATE_EXCHANGE_STRINGS (boolean)
    164      */
    165     public boolean useAlternateExchangeStrings() {
    166         return getPolicy(USE_ALTERNATE_EXCHANGE_STRINGS, null)
    167                 .getBoolean(USE_ALTERNATE_EXCHANGE_STRINGS, false);
    168     }
    169 
    170     /**
    171      * Returns additional key/value pairs for the IMAP ID string.
    172      *
    173      * Vendor function:
    174      *  Select: GET_IMAP_ID
    175      *  Params: GET_IMAP_ID_USER (String)
    176      *          GET_IMAP_ID_HOST (String)
    177      *          GET_IMAP_ID_CAPABILITIES (String)
    178      *  Result: GET_IMAP_ID (String)
    179      *
    180      * @param userName the server that is being contacted (e.g. "imap.server.com")
    181      * @param host the server that is being contacted (e.g. "imap.server.com")
    182      * @param capabilities reported capabilities, if known.  null is OK
    183      * @return zero or more key/value pairs, quoted and delimited by spaces.  If there is
    184      * nothing to add, return null.
    185      */
    186     public String getImapIdValues(String userName, String host, String capabilities) {
    187         Bundle params = new Bundle();
    188         params.putString(GET_IMAP_ID_USER, userName);
    189         params.putString(GET_IMAP_ID_HOST, host);
    190         params.putString(GET_IMAP_ID_CAPA, capabilities);
    191         String result = getPolicy(GET_IMAP_ID, params).getString(GET_IMAP_ID);
    192         return result;
    193     }
    194 
    195     public static class Provider implements Serializable {
    196         private static final long serialVersionUID = 8511656164616538989L;
    197 
    198         public String id;
    199         public String label;
    200         public String domain;
    201         public String incomingUriTemplate;
    202         public String incomingUsernameTemplate;
    203         public String outgoingUriTemplate;
    204         public String outgoingUsernameTemplate;
    205         public String incomingUri;
    206         public String incomingUsername;
    207         public String outgoingUri;
    208         public String outgoingUsername;
    209         public String note;
    210 
    211         /**
    212          * Expands templates in all of the  provider fields that support them. Currently,
    213          * templates are used in 4 fields -- incoming and outgoing URI and user name.
    214          * @param email user-specified data used to replace template values
    215          */
    216         public void expandTemplates(String email) {
    217             String[] emailParts = email.split("@");
    218             String user = emailParts[0];
    219 
    220             incomingUri = expandTemplate(incomingUriTemplate, email, user);
    221             incomingUsername = expandTemplate(incomingUsernameTemplate, email, user);
    222             outgoingUri = expandTemplate(outgoingUriTemplate, email, user);
    223             outgoingUsername = expandTemplate(outgoingUsernameTemplate, email, user);
    224         }
    225 
    226         /**
    227          * Replaces all parameterized values in the given template. The values replaced are
    228          * $domain, $user and $email.
    229          */
    230         private String expandTemplate(String template, String email, String user) {
    231             String returnString = template;
    232             returnString = returnString.replaceAll("\\$email", email);
    233             returnString = returnString.replaceAll("\\$user", user);
    234             returnString = returnString.replaceAll("\\$domain", domain);
    235             return returnString;
    236         }
    237     }
    238 
    239     /**
    240      * Returns provider setup information for a given email address
    241      *
    242      * Vendor function:
    243      *  Select: FIND_PROVIDER
    244      *  Param:  FIND_PROVIDER (String)
    245      *  Result: FIND_PROVIDER_IN_URI
    246      *          FIND_PROVIDER_IN_USER
    247      *          FIND_PROVIDER_OUT_URI
    248      *          FIND_PROVIDER_OUT_USER
    249      *          FIND_PROVIDER_NOTE (optional - null is OK)
    250      *
    251      * Note, if we get this far, we expect "correct" results from the policy method.  But throwing
    252      * checked exceptions requires a bunch of upstream changes, so we're going to catch them here
    253      * and add logging.  Other exceptions may escape here (such as null pointers) to fail fast.
    254      *
    255      * @param domain The domain portion of the user's email address
    256      * @return suitable Provider definition, or null if no match found
    257      */
    258     public Provider findProviderForDomain(String domain) {
    259         Bundle params = new Bundle();
    260         params.putString(FIND_PROVIDER, domain);
    261         Bundle out = getPolicy(FIND_PROVIDER, params);
    262         if (out != null && !out.isEmpty()) {
    263             Provider p = new Provider();
    264             p.id = null;
    265             p.label = null;
    266             p.domain = domain;
    267             p.incomingUriTemplate = out.getString(FIND_PROVIDER_IN_URI);
    268             p.incomingUsernameTemplate = out.getString(FIND_PROVIDER_IN_USER);
    269             p.outgoingUriTemplate = out.getString(FIND_PROVIDER_OUT_URI);
    270             p.outgoingUsernameTemplate = out.getString(FIND_PROVIDER_OUT_USER);
    271             p.note = out.getString(FIND_PROVIDER_NOTE);
    272             return p;
    273         }
    274         return null;
    275     }
    276 }
    277