VendorPolicyLoader.java revision bc47398187c6ffd132435e51d8d61e6ec79a79db
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
17package com.android.email;
18
19import com.android.email.activity.setup.AccountSettingsUtils.Provider;
20import com.android.emailcommon.Logging;
21
22import android.content.Context;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.PackageManager.NameNotFoundException;
25import android.os.Bundle;
26import android.util.Log;
27
28import 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 */
40public 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