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