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